Much of Lisp's power stems from the fact that virtually everything can be represented as a list. If this is true, then writing 'each for lists is almost as good as (or better than) writing a generic 'each for every kind of type. A language design like Arc's that capitalizes on this idea indirectly provides incentive for making as many things into lists as possible. This is why pg has flirted with representing both strings [1] and numbers [2] as lists, and also why he promotes using assoc lists over tables when possible [3].
One disadvantage of this approach is that it can sometimes seem unnatural to represent x as a list, but it has the benefit of providing a very minimal cloud of abstractions with maximum flexibility.
A powerful object system seems like a different way of going about the same thing. That's probably why Lisp users are often unenthusiastic about objects: there's a feeling of redundancy and of pulling their language in two different directions. (Arc users can be especially unenthusiastic because they're so anal about minimizing the abstraction cloud.) That's why I'm not particularly enthusiastic about objects, anyway. They're not worse - just different, and largely unnecessary if you have lists.
I could be missing the ship here. I don't have enough experience with object systems to understand all their potential benefits over using lists for everything. (And, of course, the popularity of CLOS demonstrates that a lot of people like to have both!)
)
[1] arc.arc has this comment toward the top:
; compromises in this implementation:
...
; separate string type
; (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail
; not sure this is a mistake; strings may be subtly different from
; lists of chars
The Lisp that McCarthy described in 1960, for example, didn't have numbers. Logically, you don't need to have a separate notion of numbers, because you can represent them as lists: the integer n could be represented as a list of n elements. You can do math this way. It's just unbearably inefficient.
I once thought alists were just a hack, but there are many things you can
do with them that you can't do with hash tables, including sort
them, build them up incrementally in recursive functions, have
several that share the same tail, and preserve old values.
I think you're right about it being frustrating to be pulled in multiple directions when choosing how to represent a data structure.
In Groovy, I'm pulled in one direction:
class Coord { int x, y }
...
new Coord( x: 10, y: 20 )
+ okay instantiation syntax
+ brief and readable access syntax: foo.x
As the project evolves, I can change the class definition to allow for a better toString() appearance, custom equals() behavior, more convenient instantiation, immutability, etc.
In Arc, I'm pulled in about six directions, which are difficult to refactor into each other:
'(coord 10 20)
+ brief instantiation syntax
+ brief write appearance: (coord 10 20)
+ allows (let (x y) cdr.foo ...)
- no way for different types' x fields to be accessed using the same
code without doing something like standardizing the field order
(obj type 'coord x 10 y 20)
+ brief and readable access syntax: do.foo!x (map !x foos)
+ easy to supply defaults via 'copy or 'deftem/'inst
[case _ type 'coord x 10 y 20]
+ immutability when you want it
+ brief and readable access syntax: do.foo!x (map !x foos)
- mutability much more verbose to specify and to perform
(annotate 'coord '(10 20))
+ easy to use alongside other Arc types in (case type.foo ...)
+ semantically clear write appearance: #(tagged coord (10 20))
+ allows (let (x y) rep.foo ...)
- no way for different types' x fields to be accessed using the same
code without doing something like standardizing the field order
(annotate 'coord (obj x 10 y 20))
+ easy to use alongside other Arc types in (case type.foo ...)
+ okay access syntax: rep.foo!x (map !x:rep foos)
(annotate 'coord [case _ x 10 y 20])
+ immutability when you want it
+ easy to use alongside other Arc types in (case type.foo ...)
+ okay access syntax: rep.foo!x (map !x:rep foos)
- mutability much more verbose to specify and to perform
(This doesn't take into account the '(10 20) and (obj x 10 y 20) forms, which for many of my purposes have the clear disadvantage of carrying no type information. For what it's worth, Groovy allows forms like those, too--[ 10, 20 ] and [ x: 10, y: 20 ]--so there's no contrast here.)
As the project goes on, I can write more Arc functions to achieve a certain base level of convenience for instantiation and field access, but they won't have names quite as convenient as "x". I can also define completely new writers, equality predicates, and conditional syntaxes, but I can't trust that the new utilities will be convenient to use with other programmers' datatypes.
In practice, I don't need immutability, and for some unknown reason I can't stand to use 'annotate and 'rep, so there are only two directions I really take among these. Having two to choose from is a little frustrating, but that's not quite as frustrating as the fact that both options lack utilities.
Hmm, that gives me an idea. Maybe what I miss most of all is the ability to tag a new datatype so that an existing utility can understand it. Maybe all I want after all is a simple inheritance system like the one at http://arclanguage.org/item?id=11981 and enough utilities like 'each and 'iso that are aware of it....
I rewrote the type system for arc a while ago, so that it would support inheritance and generally not get in the way, but unfortunately I haven't had the time to push it yet. If you're interested, I could try to get that up some time soon.
Well, I took a break from wondering what I wanted, and I did something about it instead, by cobbling together several snippets I'd already posted. So I'm going to push soon myself, and realistically I think I'll be more pleased with what I have than what you have. For instance, Mine is already well-integrated with my multival system, and it doesn't change any Arc internals, which would complicate Lathe's compatibility claims.
On the other hand, and at this moment it's really clear to me that implementing generic doppelgangers of arc.arc functions is a bummer when it comes to naming, and modifying the Arc internals to be more generic, like you've done (right?), could really make a difference. Maybe in places like that, your approach and my approach could form an especially potent combination.
I finally pushed this to Lathe. It's in the new arc/orc/ folder as two files, orc.orc and oiter.arc. The core is orc.arc, and oiter.arc is just a set of standard iteration utilities like 'oeach and 'opos which can be extended to support new datatypes.
The main feature of orc.arc is the 'ontype definition form, which makes it easy to define rules that dispatch on the type of the first argument. These rules are just like any other rules (as demonstrated in Lathe's arc/examples/multirule-demo.arc), but orc.arc also installs a preference rule that automatically prioritizes 'ontype rules based on an inheritance table.
It was easy to define 'ontype, so I think it should be easy enough to define variants of 'ontype that handle multiple dispatch or dispatching on things other than type (like value [0! = 1], dimension [max { 2 } = 2], or number of arguments [atan( 3, 4 ) = atan( 3/4 )]). If they all boil down to the same kinds of rules, it should also be possible to use multiple styles of dispatch for the same method, resolving any ambiguities with explicit preference rules. So even though 'ontype itself may be limited to single dispatch and dispatching on type, it's part of a system that isn't.
Still, I'm not particularly sure orc.arc is that helpful, 'cause I don't even know what I'd use it for. I think I'll only discover its shortcomings and its best applications once I try using it to help port some of my Groovy code to Arc.
And yes, I did modify arc's internals to be more generic. Basically, I replaced the vectors pg used for typing with lists and added the ability to have multiple types in the list at once. Since 'type returns the whole list, and 'coerce looks for conversion based on each element in order, we get a simple form of inheritance and polymorphism, and objects can be typed without losing the option of being treated like their parents.