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.