From: Brian Mastenbrook
Subject: Hygienic macros for Common Lisp
Date: 
Message-ID: <180320042144028839%NObmastenbSPAM@cs.indiana.edu>
With all of this discussion of the difference between macro systems, I
decided to put my two cents in and implement macro hygiene in defmacro,
via explicit renaming. Of course this can't be done without some kind
of a codewalker, and that's exactly how I've done it - as a codewalker
implemented as a macro. It crawls code enough to understand and rename
bindings, by implementing walkers/renamers for all of the standard
Common Lisp special forms and two OpenMCL special operators. If your
implementation has common extra special operators, you will need to
implement those too.

A basic hygienic macro looks like:

(buffalo ((buffalo (buffalo) `(buffalo ,(buffalo buffalo)))) (buffalo
buffalo))

Er, um:

(defhygienic add-two-to-n (n)
  `(let ((x 2))
     (+ x ,(rename n))))

(rename n) causes n to be renamed; there is an optional parameter for
the environment, which defaults to the current environment.

You can find the code at:

http://www.cs.indiana.edu/~bmastenb/software/cl-hygiene/cl-hygiene.lisp

There are some tests and examples at:

http://www.cs.indiana.edu/~bmastenb/software/cl-hygiene/cl-hygiene-tests.
lisp

I am sure it has bugs all over it. However, it works as a proof of
concept. Now if we could get implementations to provide this
interface...

Brian

-- 
Brian Mastenbrook
http://www.cs.indiana.edu/~bmastenb/

From: David Fisher
Subject: Re: Hygienic macros for Common Lisp
Date: 
Message-ID: <14030ca9.0403190613.56073fbe@posting.google.com>
Brian Mastenbrook <··············@cs.indiana.edu> wrote in message news:<·································@cs.indiana.edu>...

> 
> (defhygienic add-two-to-n (n)
>   `(let ((x 2))
>      (+ x ,(rename n))))
> 
> (rename n) causes n to be renamed; there is an optional parameter for
> the environment, which defaults to the current environment.

Why do you call them "hygienic"? Or, to put it differently, what
properties do they guarantee?
From: Brian Mastenbrook
Subject: Re: Hygienic macros for Common Lisp
Date: 
Message-ID: <190320041029147160%NObmastenbSPAM@cs.indiana.edu>
In article <····························@posting.google.com>, David
Fisher <·············@yahoo.com> wrote:

> Brian Mastenbrook <··············@cs.indiana.edu> wrote in message
> news:<·································@cs.indiana.edu>...
> 
> > 
> > (defhygienic add-two-to-n (n)
> >   `(let ((x 2))
> >      (+ x ,(rename n))))
> > 
> > (rename n) causes n to be renamed; there is an optional parameter for
> > the environment, which defaults to the current environment.
> 
> Why do you call them "hygienic"? Or, to put it differently, what
> properties do they guarantee?

The way this system works is via explicit renaming - let's look at an
example:

(let ((x -1))
    (add-two-to-n x))

This expands to something equivalent to the following:

(let ((x -1))
  (let ((#:x1 2))
    (+ #:x1 x)))

Where the two #:x1's are equal. Thus what is guaranteed is that any
lexical bindings (variables, functions, blocks, and go tags) will be
renamed within the expansion of the macro; the (rename) function causes
its argument to be renamed within the context of the use of the macro.
It takes an extra argument for an environment; the current environment
at invocation of the macro can be obtained with the
(current-environment) function.

The explicit call to rename is still necessary because it is not
possible to automatically tell which parts of a macro's output will be
evaluated and which parts will be consumed unevaluated by another
macro. Think of it as the same as DATUM->SYNTAX-OBJECT.

-- 
Brian Mastenbrook
http://www.cs.indiana.edu/~bmastenb/
From: Joe Marshall
Subject: Re: Hygienic macros for Common Lisp
Date: 
Message-ID: <oeqssvms.fsf@ccs.neu.edu>
·············@yahoo.com (David Fisher) writes:

> Brian Mastenbrook <··············@cs.indiana.edu> wrote in message news:<·································@cs.indiana.edu>...
>
>> 
>> (defhygienic add-two-to-n (n)
>>   `(let ((x 2))
>>      (+ x ,(rename n))))
>> 
>> (rename n) causes n to be renamed; there is an optional parameter for
>> the environment, which defaults to the current environment.
>
> Why do you call them "hygienic"? Or, to put it differently, what
> properties do they guarantee?

Hygienic:

(let ((x 33))
  (add-two-to-n x)) =>  35

Non-hygienic:

(let ((x 33))
  (add-two-to-n x)) => 4

They guarantee that the user cannot get a bogus expansion by an
unfortunate choice in naming.
From: David Fisher
Subject: Re: Hygienic macros for Common Lisp
Date: 
Message-ID: <14030ca9.0403191055.3c085696@posting.google.com>
Joe Marshall <···@ccs.neu.edu> wrote in message news:<············@ccs.neu.edu>...
> ·············@yahoo.com (David Fisher) writes:
> 
> > Brian Mastenbrook <··············@cs.indiana.edu> wrote in message news:<·································@cs.indiana.edu>...
> >
> >> 
> >> (defhygienic add-two-to-n (n)
> >>   `(let ((x 2))
> >>      (+ x ,(rename n))))
> >> 
> >> (rename n) causes n to be renamed; there is an optional parameter for
> >> the environment, which defaults to the current environment.
> >
> > Why do you call them "hygienic"? Or, to put it differently, what
> > properties do they guarantee?
> 
> Hygienic:
> 
> (let ((x 33))
>   (add-two-to-n x)) =>  35
> 
> Non-hygienic:
> 
> (let ((x 33))
>   (add-two-to-n x)) => 4
> 
> They guarantee that the user cannot get a bogus expansion by an
> unfortunate choice in naming.

I don't have OpenMCL to test, but what happens when you do

;; in own package maybe, shadowing 'let', whatever...

(macrolet ((let (&rest) 2004))
  (add-two-to-n 2))

If it's anything but 2 + 2 = 4, the proposed macro system is not
hygienic, sorry. Even if you get 4, why use RENAME explicitly?
From: Brian Mastenbrook
Subject: Re: Hygienic macros for Common Lisp
Date: 
Message-ID: <190320041544246374%NObmastenbSPAM@cs.indiana.edu>
In article <····························@posting.google.com>, David
Fisher <·············@yahoo.com> wrote:

> I don't have OpenMCL to test, but what happens when you do
> 
> ;; in own package maybe, shadowing 'let', whatever...
> 
> (macrolet ((let (&rest) 2004))
>   (add-two-to-n 2))
> 
> If it's anything but 2 + 2 = 4, the proposed macro system is not
> hygienic, sorry. Even if you get 4, why use RENAME explicitly?

Actually right now I'm ignoring the interaction between MACROLET and
the renamer, which is probably an issue. Even then, shadowing LET kind
of distorts the issue - you can't shadow it by binding, only in a
different package. Shadowing LET's binding is not compliant Common
Lisp.

However, something like:

(flet ((+ (&rest args) (apply #'- args))) (add-two-to-n 2))

works IFF the renamer is already active.

BTW, it doesn't require OpenMCL, merely supports it. OpenMCL has two
common extra special forms - compiler-let and nfunction (for named
lambdas).

As far as why RENAME is needed, how else can you tell whether an
argument needs to be renamed or whether it will wind up unevaluated /
destructured by a macro?

-- 
Brian Mastenbrook
http://www.cs.indiana.edu/~bmastenb/