|2 months ago I showed this bug in wart:|
arc> (iso :x :x)
This is now fixed. In the process I had to overhaul how wart binds params in fn. It should be a lot more robust, but the code has gotten a lot more complected. I'll try to explain why.
In broad terms, traditional lisp evaluators handle lambda binding as follows:
When I implemented pervasive keyword args for all params back in 2011 I changed this to:
Eval all args -> bind sequentially.
Basically it works by first 'pinching out' all keyword args into a table, then successively binding args while looking for candidates in the table.
Reorder all args -> Eval all args -> bind sequentially.
However, wart also added quoted parameters at some point. Useful as a primitive to build macros out of. You could quote a parameter to suppress evaluation of corresponding args at call time. But I kept having to add special cases to support this in the second, eval step above. Eventually, in early 2013 I switched to:
A year later I started noticing more issues. The culprit was parameter aliases, which let me give params distinct names for keyword args. In wart, aliases are fully general and support haskell-like as-patterns (http://www.haskell.org/tutorial/patterns.html):
Reorder all args -> Bind in param sequence, eval'ing as necessary.
Here foo will receive an arg called xs, whose car is bound to head, and cdr to tail (the ellipses are like dotted lists in regular scheme/arc). Very useful feature for building generic functions and so on; it let me very straightforwardly overlay features of def and mac atop one another (https://github.com/akkartik/wart/blob/96d90427d9/047generic.wart). However, the cost was additional complexity that I thoroughly under-estimated. What happens if you alias some quoted and some unquoted params, in various orders? In many cases I was silently interpreting the cons inside the alias as a destructured param, which required first eval'ing it, causing the above bug. There were other issues, like this:
def (foo (xs | (head ... tail))
Should params be (1 :x 2) or (2 1)? Should the answer change based on whether it's quoted or not? This and other issues basically convinced me that reordering args in advance was no longer defensible. Instead, I am now at this monstrosity:
def (foo ... ('params | (x y)))
(foo 1 :x 2)
All structure is gone. But I have a lot more tests and things seem to work much better. Hopefully structure will return in time as I gradually work through possible refactorings.
In param sequence, bind keyword or position args eval'ing as necessary.
Anyway, I'm still unconvinced that these features are all a good idea as I said before. But at least I have a working prototype to judge over time.
A summary of code changes: https://github.com/akkartik/wart/compare/8bcd5212c...96d90427d9
 The answer to this question is now always (1 :x 2) regardless of whether or not params is quoted. So I can afford to not have to reorder args when binding it. One new constraint is that param aliases can only include conses and as-params at the end. So this is illegal:
But these are legal and supported:
(a | (b c) | d)
(a | d | (b c))
(a | (b ... (c | (d ... (e | (f g))))))