To recap: xonses are like conses, but in addition to car and cdr, they may have any number of other slots.
() == {} ;; nil is just an empty xons
(a) == { :car a :cdr {} }
(:foo 1) == { :foo 1 }
(:foo 1 2 3) == { :foo 1 :car 2 :cdr { :car 3 :cdr {} } }
(open-file "README" :mode "rw" :create-if-not-exists #t) ==
{ :mode "rw" :create-if-not-exists #t :car "open-file" :cdr { :car "README" :cdr {} } }
The differences between a Lisp/Kernel with conses and one with xonses are mostly:
- There is no longer a separate nil. A xons without car and cdr is treated as nil, even if it has other slots.
- The usual pair? predicate changes from (instance? obj Pair) to (and? (instance? obj Pair) (not? (null? obj)).(These previous two rules have to do with the fact that xonses are more expressive than conses, and so we lose some "sharpness".)
- Pattern matching is extended, so that not only car and cdr are matched recursively, but all slots.
- Evaluation of arguments is extended: like in Kernel, an applicative cdrs down its operands and evaluates each, building the arguments list. In addition we now also evaluate the other slots of the first xons (Open question: should we also evaluate the other slots of later xonses? Probably yes).
All in all, xonses fit into the usual Lisp framework.
One big problem remains, which is why my new interpreter doesn't work:
In the x-expression (open-file "README" :mode "rw"), the :mode slot gets attached to the first xons, the one whose car is open-file. Per the usual evaluation rule, a combiner uses the cdr -- ("README") -- of the form it appears in as operand, and so it doesn't see the :mode slot.
I think it boils down to this: once we have arbitrary slots and not just car and cdr, it may make sense to remove the usual Lisp rule that the car of a combination contains its combiner. Maybe we need an explicit combiner slot:
(:combiner open-file "README" :mode "rw")
which corresponds to
(:combiner open-file :mode "rw" :car "README" :cdr ())
or abbreviated, using \ for :combiner.
(\open-file "README" :mode "rw")
Mirroring the fact that there's no nil, this also has the nice symmetry that calling a combiner without arguments requires no car and cdr anymore:
(\foo) means simply (:combiner foo), without car and cdr.
Mirroring the fact that there's no nil, this also has the nice symmetry that calling a combiner without arguments requires no car and cdr anymore:
(\foo) means simply (:combiner foo), without car and cdr.
Investigating!