[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: hygiene when using multiple instances of a macro..?

This page is part of the web mail archives of SRFI 72 from before July 7th, 2015. The new archives for SRFI 72 contain all messages, not just those from before July 7th, 2015.

> From: Andre van Tonder <andre@xxxxxxxxxxxxxxxxx>
> Thank you for the example, but shouldn't that be instead:
>    (define-syntax (can-we-stand-duplicates a-macro)
>      (quasisyntax
>       (if ',a-macro                     ; note quote
>           (let ((x 3)) (,a-macro #f))
>           x)))
>    (test)    ;==> reference to unidentified identifier: x#top

> On Mon, 8 Aug 2005, Panu wrote:
> > ... if it works wrong, it expands to (something that evaluates to) 3.
> > If it works right, it expands to something that has an unbound
> > identifier.

I didn't see why the quote is needed, so I ran the original
example.  When I got an undefined identifier I thought all
was well, and was about to post a defence of Panu.  Then
I noticed it was the _wrong_ undefined identifier.

After an afternoon of breaking things and fixing them again
I was a happier but wiser man.  In the course of my adventures
I noticed the following code in the reference implementation.

(define (expand-define-syntax t)
  (let ((t (normalize-definition t #t))
        (r (make-primitive-renaming-procedure)))
    (bind-toplevel! (cadr t))
     `(register-macro ',(binding-name (cadr t))
                      ,(expand (caddr t)))))

This expands a form like 

 (define-syntax (test)
   #`(can-we-stand-duplicates can-we-stand-duplicates))

into a call to the |register-macro| procedure called with something
that looks like

      ((lambda (@test_406)
	 (lambda (@form_407)
	   (apply @test_406 @form_407))) ; make |test| take zero args
       (lambda (@dummy_408)
         ; make a renamer to apply to all ids in the quasisyntax
	 (let ((rename_412 (make-meta-renaming-procedure)))
           ; build the syntax list out of renamed ids
	   (cons (rename_412 (quote _)
			     (quote can-we-stand-duplicates)
			     (quote can-we-stand-duplicates_)
			     (quote __417))
		 (cons (rename_412 (quote _)
				   (quote can-we-stand-duplicates)
				   (quote can-we-stand-duplicates_)
				   (quote __417))
		       (quote ()))))))))

It is then left to the execution phase of the reepl
(read-expand-execute-print loop) to evaluate that lambda
expression and register it in the *syntax-environment*.
Since the reepl always evaluates the top-level form right
after expanding it, this gets the correct result in the
end, but if you wanted your expand phase to produce an
expanded form that could be executed separately, then this
would be wrong.  You would want you lambda form evaluated
to a procedure and registered _by the expander_.

On the other hand, I was having so much fun looking at
the raw lambda expressions that I did not want to evaluate
them before I could see them.  So I changed it to:

  (define (expand-define-syntax t)
    (let ((t (normalize-definition t #t))
          (r (make-primitive-renaming-procedure)))
      (bind-toplevel! (cadr t))
      (let ((proc-source (expand (caddr t))))
        (register-macro (binding-name (cadr t))
                        (eval proc-source))
      `(registered-macro ',(binding-name (cadr t))

(define (registered-macro name proc) (void))

This evaluates and registers the transformer procedure
in the expander phase and expands to something that
does nothing and yields void at execution time.
But it does nothing in a way that is fun to watch.

By the way, my transformer procedure looks a bit different
from yours because I had to change all the sharp signs #\#
in the identifiers into underscores so that my Scheme
could read them.  I have a reepl like procedure that
goes through the file of test cases and compares against
the correct answers.

     -- Keith Wright

Programmer in Chief, Free Computer Shop
 ---  Food, Shelter, Source code.  ---