Arc Forumnew | comments | leaders | submitlogin
Intuitive hygienic macros (
10 points by rntz 4173 days ago | 7 comments

6 points by rntz 4173 days ago | link

This is long overdue, but I've finally posted an explanation of the implementation of my hygienic macro system for arc ( 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.


1 point by rkts 4168 days ago | link

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 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:

  (mac count (x) `(++ counter ,x))

  (let counter 0 (count 3) counter)
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?


2 points by rntz 4168 days ago | link

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))
              (if ,@(mappend
                      (fn (u x) (list `(< (-- r ,u) 0) `#,x))
                      us xs))))))
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
      #`((cadr:bias-elt car
           (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:

    (mac count (x) #`(++ #,'counter #,x))


1 point by rntz 4168 days ago | link

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.


1 point by rkts 4166 days ago | link

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.


1 point by rkts 4166 days ago | link

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.


1 point by rntz 4166 days ago | link

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.