This is long overdue, but I've finally posted an explanation of the implementation of my hygienic macro system for arc (http://arclanguage.org/item?id=8599). It's written from a general lisper's perspective, because the principles involved aren't arc-specific. It doesn't yet cover the modifications to the internals of ac.scm.
Very nice. I don't fully understand it yet, but it looks like it may be the best solution to the hygiene problem I've seen. Two questions:
1. Because #`() doesn't produce lists, you can't use standard list manipulation functions on the resulting code. I tried writing this macro http://arclanguage.org/item?id=7773 in your system but couldn't because the macro produces expressions with mappend, which doesn't work on your "vec" type. Is there a way to fix this?
2. Is it possible for a macro to refer to variables in the calling environment? Like this:
This is of dubious utility, but still. I thought I could make it work in your system by changing counter to #,'counter, but then I get a cryptic error. Is this a bug or am I misunderstanding something?
Both of these can be done; they are in fact both instances of the "exceptional cases" I talk about under "Hygienic quasiquotation". I should probably have provided examples, so I'll do so now.
In order to do (1), you have to break up the stuff that's getting mappended. It needs to be a list, but it needs to contain syntactic unquotations. I think an example will explain better than words here, so here's your macro written with my hygienic quasiquotes:
(mac bias args
(withs (ws (map car pair.args)
xs (map cadr pair.args)
us (map [uniq] ws))
#`(with ,(mappend (fn (u w) `(,u #,w)) us ws)
(let r (rand (+ ,@us))
(fn (u x) (list `(< (-- r ,u) 0) `#,x))
To be honest, this macro doesn't benefit much from hygiene, since you still need to call 'uniq. However, if I were to write this, I'd do it by putting the biasing logic in a function and wrapping it in a macro, which considerably reduces the amount of wrangling you have to do to get it to work with my system:
(def bias-elt (weight lst)
(let r (rand (apply + (map weight lst)))
(catch:each e lst
(if (< (-- r weight.e) 0) throw.e))))
(mac bias args
(list ,@(mapeach (w x) pair.args
`(list #,w (fn () #,x)))))))
To implement (2), what you do is quite simple, and it's obvious why it works, but it's a bit nonintuitive:
Oops, my example for (2) is broken. I see why it doesn't work, and it's rather interesting. It doesn't work because '++ is a _macro_ that expands to '(= <counter> (+ 1 <counter>)), where <counter> is the syntactic closure of 'counter. Then '= in turn gets macroexpanded, but '= does its own examination of the code to determine what to do (because of setter functions, etc) and it isn't written with closures in mind. I may need to look into this.
This does, however, serve to highlight the main problem with adding hygiene this way: it adds a new kind of data structure (closures) to what is considered "code", and anything that manipulates could need to be updated to handle this new case.
Wow, that's confusing. I was hoping that #` and #, could fully replace ` and , (at least in macros), so you don't have to juggle the two kinds of quasiquote.
Would it be possible to write an mappend that joins hygienically-quoted lists? It would return an ordinary list with each element inside a closure. Then we could write the first example without using the ordinary quasiquote.
Ok, I think I get it now. If we gave hygienically-quoted expressions to mappend, they would be in a different closure than the surrounding code and therefore couldn't see the variables it introduced (r, in our case). So I guess you might say that #` should usually be used at the start of a macro, and ` after that. Still confusing.
It's true, it's not quite as intuitive as I'd like it to be: I, too, originally envisioned it entirely replacing normal quasiquoting for macros, but this turned out more difficult than I expected. It might be possible to make it more intuitive, but at the moment I'm not really working on it; this is an after-the-fact explanation more than a design document. I'm presently in the process of creating a low-level systems programming language with Arc (or at least a Lisp) as its metalanguage; when I get the the problem of adding macros to that language I'll probably give this another look.