Arc Forumnew | comments | leaders | submitlogin
Hygienic Macros
4 points by PieSquared 6108 days ago | 15 comments
One of the problems with macros, it seems, is hygiene - a problem which seems to be on a lot of people's minds considering Arc is a new Lisp variant.

I've tried doing some googling of my own and looking up on how hygienic macros are implemented. However, I haven't really found anything - yet in discussions of hygiene (macros) it seems that people often refer to a way to actually make them. Could someone perhaps post a link to some sites, or give an explanation of the 'standard' way to do hygienic macros, if there is one? (And from what I've understood... Scheme's macros don't REALLY give you hygiene.)

Also, one of the problems with Common-Lisp-like macros is variable capture, right? As in, a function gets shadowed by a local variable, so the macro ends up producing code that calls the wrong function or just fails. (Correct me if I'm saying this the wrong way or if I'm just plain wrong :)) But, couldn't this be solved with a module system?

I was thinking that with a Java-like package system, you could just refer to the function you actually mean. Like you can refer to classes by their full name package.name.ClassName, you could refer to functions and macros the same way.

Er... am I solving a non-existent problem here? Because the way I understood it, that was one of the problems with non-hygienic macros.

Anyway, what do you think?



4 points by kennytilton 6107 days ago | link

Sorry (really) but I am afraid it's "just plain wrong". Variable capture is a feature, not a bug. It happens only if the macro author wants it to (on pretty rare occasions) or if they need to be fired for not using gensym to create an uninterned symbol which cannot possibly clash with another name/symbol.

As for a macro expanding into a larger lexical space where "list" has been bound to, say, 42, one of the joys of CL is never having to worry about that and we do it all the time (and its a bit of a shibboleth when we see people using "lst" in code they mention on comp.lang.lisp, we unleash the hounds who chase them over the fence to comp.lang.scheme where they belong).

I have written hundreds of macros including macro-generating macros over thirteen years and the only problem I ever has is figuring out nested backquotes, but that is because I just never managed to form a mental model of WTF is going on <g>, and it only takes me a couple of trials and errors to get past it.

There really is no problem with CL unhygienic macros. Oh, I should have mentioned that:

  (let ((list 42))
    (list 'hi 'mom))
... works fine and produces '(HI MOM) because CL is a Lisp-2 and shrugs off the variable name (in the operator position, variables do not apply (ugh, pun not intended). As for:

  (defun xxx ()
     (flet ((list (&rest args)
              (car args)))
        (list 'hi 'mom)))
...fine, here's another fine mess you've gotten me into (when I try to compile):

; While compiling XXX in C:\DOCUME~1\Kenny\LOCALS~1\Temp\cda9402357291.cl: Warning: Compiling a FUNCTION definition for the name LIST as a FLET. This name is in the COMMON-LISP package and defining it will be a violation for portable programs. The package COMMON-LISP has PACKAGE-DEFINITION-LOCK set, which causes the system to signal this violation.

The good news is that you are right about one thing, in CL we can use lib-x:same-name-func and lib-y:same-name-func to specify which we want should a clash arise.

-----

3 points by KirinDave 6107 days ago | link

And a fine argument that is. The need for hygenic macros is overstated.

What I don't see is why you can't provide both? It's not confusing to provide both, it's not difficult to provide both. There are that class of macros you write where variable capture is tedious to deal with manually, so why not save some code and make a correct implementation of hygenic macros just so that people who need them don't have to re-implement them?

-----

3 points by kennytilton 6106 days ago | link

I just counted up again and my code base shows 613 defmacro occurences and only 111 gensym occurences and I do not even use a with-gensyms macro, so I am not sold on the need. Then again, I do strive for the functional thang and try not even to use let. btw, I am not fastidious generally but (I guess cuz it is so easy) I never skip on a gensym -- I am definitely of the school that believes programming is hard enough as it is. :)

-----

3 points by raymyers 6107 days ago | link

While we're on the subject, I know a number of people have been saying that Arc's unhygenic macros problems are a problem, because things like this break in a lisp-1:

    (mac break (a b) `(list ,a ,b))
    (let list nil (break 1 2))  =>  Error
However, you can simply unquote the function values in the macro.

    (mac dont-break (a b) `(,list ,a ,b))
    (let list nil (dont-break 1 2))  =>  (1 2)

-----

3 points by cadaver 6107 days ago | link

To make this work for functions, you need to add ((procedure? s) s) to 'ac. Otherwise with arc2.tar:

  arc> (mac dont-break (a b) `(,list ,a ,b))
  #3(tagged mac #<procedure>)
  arc> (let list nil (dont-break 1 2))
  Error: "Bad object in expression #<procedure: list>"
Macros still remain a problem:

  (mac list-macro parms `(,list ,@parms))
  (mac break (a b) `(,list-macro ,a ,b))
  (break 1 2) => Error

-----

2 points by raymyers 6107 days ago | link

I stand corrected, it worked in Anarki :)

And yeah, it obviously wouldn't work for macros, but macros don't get shadowed by let blocks in the first place.

    (mac list-macro args `(list ,@args))
    (mac dont-break (a b) `(list-macro ,a ,b))
    (let list-macro nil (dont-break 1 2))  =>  (1 2)

-----

1 point by cadaver 6107 days ago | link

Then it is I who shall stand corrected :)

-----

2 points by bogomipz 6107 days ago | link

A problem with unquoting the function value is that if you redefine the function, any code that uses the macro will hold on to the original function as a literal. This means the solution doesn't lend itself well to exploratory programming.

-----

2 points by raymyers 6107 days ago | link

Yes, it is true that preventing redefinition will in fact prevent redefinition. Bear that in mind when preventing redefinition :)

-----

1 point by bogomipz 6107 days ago | link

Well, one could create a module system so that the macro would always see the function the way it was seen in the module where the macro was defined. Then, when the function name is rebound where the macro is applied, it doesn't affect the macro, but if the function is recompiled in its original module, it does affect the code produced by the macro. I'm not saying it's straight forward, but I do believe it's possible.

-----

4 points by sacado 6107 days ago | link

"when we see people using "lst" in code they mention on comp.lang.lisp, we unleash the hounds who chase them over the fence to comp.lang.scheme where they belong" : be careful, it's not necessarily a schemer, it might also be pg, writing a function where both l and ls are already bound...

-----

4 points by kennytilton 6107 days ago | link

Excellent point, it might just be an Arc welder trying to save a char -- well, we checked with the hounds and they say a CL-style defmacro and (is nil #f) -> t don't mask the Lisp-1 scent, but they loved On Lisp and Ansi Common Lisp and ViaWeb being done in CL so pg is welcome any time.

-----

2 points by raymyers 6107 days ago | link

Well interestingly enough, li ls and lst don't appear in the non-Scheme source of Arc, except for lst in the borrowed mergesort.

What about xs and ys, do we get the hounds too?

-----

1 point by kennytilton 6107 days ago | link

Looks like my code when it's a three-liner and it really is a bit much to be coding (in my Algebra program):

  (loop for denominator in denominators...
You are right (guessing at the implicit): "list" is a terrible name for a variable unless someone really is writing a general purpose list manipulation function, but we do see "lst" quite a bit over on c.l.lisp.

btw, the real question is whether you see "list" as a variable name in the Scheme source.

-----

1 point by tel 6107 days ago | link

xs, ys, as, bs set off my Haskell alarm. They're pretty obvious and general, but something about pattern matching makes them unavoidable:

   interleve [] _ = []
   interleve _ [] = []
   interleve (x:xs) (y:ys) = x:y:(interleve xs ys)

-----