For Gauche 0.9.5


Next: , Previous: , Up: Macros   [Contents][Index]

5.2 Hygienic macros

Macro bindings

The following forms establish bindings of name and a macro transformer created by transformer-spec. The binding introduced by these forms shadows a binding of name established in outer scope, if there’s any.

For toplevel bindings, it will shadow bindings of name imported or inherited from other modules (see Modules). (Note: This toplevel shadowing behavior is Gauche’s extension; in R7RS, you shouldn’t redefine imported bindings, so the portable code should avoid it.)

The effect is undefined if you bind the same name more than once in the same scope.

The transformer-spec can be either one of syntax-rules form, er-macro-transformer form, or another macro keyword or syntactic keyword. We’ll explain them later.

Special Form: define-syntax name transformer-spec

[R7RS] If this form appears in toplevel, it binds toplevel name to a macro transformer defined by transformer-spec.

If this form appears in the declaration part of body of lambda (internal define-syntax), let and other similar forms, it binds name locally within that body. Internal define-syntaxes are converted to letrec-syntax, just like internal defines are converted to letrec*.

Special Form: let-syntax ((name transformer-spec) …) body
Special Form: letrec-syntax ((name transformer-spec) …) body

[R7RS] Defines local macros. Each name is bound to a macro transformer as specified by the corresponding transformer-spec, then body is expanded. With let-syntax, transformer-spec is evaluated with the scope surrounding let-syntax, while with letrec-syntax the bindings of names are included in the scope where transformer-spec is evaluated. Thus letrec-syntax allows mutually recursive macros.

Transformer specs

The trasformer-spec is a special expression that evaluates to a macro transformer. It is evaluated in a different phase than the other expressions, since macro transformers must be executed during compiling. So there are some restrictions.

At this moment, only one of the following expressions are allowed:

  1. A syntax-rules form. This is called “high-level” macro, for it uses pattern matching entirely, which is basically a different declarative language from Scheme, thus putting the complication of the phasing and hygiene issues completely under the hood. Some kind of macros are easier to write in syntax-rules. See Syntax-rules pattern langauge, for further description.
  2. An er-macro-transformer form. This employs explicit-renaming (ER) macro, where you can use arbitrary Scheme code to transform the program, with required renaming to keep hygienity. The legacy Lisp macro can also be written with ER macro if you don’t use renaming. See Explicit-renaming macro transformer, for the details.
  3. Macro or syntax keyword. This is Gauche’s extension, and can be used to define alias of existing macro or syntax keyword.
    (define-syntax si if)
    (define écrivez write)
    
    (si (< 2 3) (écrivez "oui"))
    

Next: , Previous: , Up: Hygienic macros   [Contents][Index]

5.2.1 Syntax-rules pattern langauge

Special Form: syntax-rules (literal …) clause1 clause2 …
Special Form: syntax-rules ellipsis (literal …) clause1 clause2 …

[R7RS] This specifies a macro transformer by pattern matching.


Previous: , Up: Hygienic macros   [Contents][Index]

5.2.2 Explicit-renaming macro transformer

Special Form: er-macro-transformer procedure-expr

Creates a macro transformer from the given procedure-expr. The created macro transformer has to be bound to the syntactic keyword by define-syntax, let-syntax or letrec-syntax. Other use of macro transformers is undefined.

The procedure-expr must evaluate to a procedure that takes three arguments; form, rename and id=?.

The form argument receives the S-expression of the macro call. The procedure-expr must return an S-expression as the result of macro expansion. This part is pretty much like the traditional lisp macro. In fact, if you ignore rename and id=?, the semantics is the same as the traditional (unhygienic) macro. See the following example (Note the use of match; it is a good tool to decompose macro input):

(use util.match)

;; Unhygienic 'when-not' macro
(define-syntax when-not
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test expr1 expr ...)
         `(if (not ,test) (begin ,expr1 ,@expr))]
        [_ (error "malformed when-not:" form)]))))

(macroexpand '(when-not (foo) (print "a") 'boo))
  ⇒ (if (not (foo)) (begin (print "a") 'boo))

This is ok as long as you know you don’t need hygiene—e.g. when you only use this macro locally in your code, knowning all the macro call site won’t contain name conflicts. However, if you provide your when-not macro for general use, you have to protect namespace pollution around the macro use. For example, you want to make sure your macro work even if it is used as follows:

(let ((not values))
  (when-not #t (print "This shouldn't be printed")))

The rename argument passed to procedure-expr is a procedure that takes a symbol (or, to be precise, a symbol or an identifier) and effectively renames it to a unique identifier that keeps identity within the macro definition environment and won’t be affected in the macro use environment.

As a rule of thumb, you have to pass all new identifiers you insert into macro output to the rename procedure to keep hygiene. In our when-not macro, we insert if, not and begin into the macro output, so our hygienic macro would look like this:

(define-syntax when-not
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test expr1 expr ...)
         `(,(rename 'if) (,(rename 'not) ,test)
            (,(rename 'begin) ,expr1 ,@expr))]
        [_ (error "malformed when-not:" form)]))))

This is cumbersome and makes it hard to read the macro, so Gauche provides an auxiliary macro quasirename, which works like quasiquote but renaming identifiers in the form. See the entry of quasirename below for the details. You can write the hygienic when-not as follows:

(define-syntax when-not
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test expr1 expr ...)
         (quasirename rename
           (if (not ,test) (begin ,expr1 ,@expr)))]
        [_ (error "malformed when-not:" form)]))))

You can intentionally break hyginene by inserting a symbol without renaming. The following code implements anaphoric when, meaning the result of the test expression is available in the expr1 exprs … with the name it. Since the binding of the identifier it does not exist in the macro use site, but rather injected into the macro use site by the macro expander, it is unhygienic.

(define-syntax awhen
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test expr1 expr ...)
         `(,(rename 'let1) it ,test     ; 'it' is not renamed
             (,(rename 'begin) ,expr1 ,@expr))]))))

If you use quasirename, you can write ,'it to prevent it from being renamed:

(define-syntax awhen
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test expr1 expr ...)
         (quasirename rename
           (let1 ,'it ,test
             (begin ,expr1 ,@expr)))]))))

Here’s an example:

(awhen (find odd? '(0 2 8 7 4))
  (print "Found odd number:" it))
 ⇒ prints Found odd number:7

Finally, the id=? argument to the procedure-expr is a procedure that takes two arguments, and returns #t iff both are identifiers and either both are referring to the same binding or both are free. It can be used to compare literal syntactic keyword (e.g. else in cond and case forms) hygienically.

The following if=> macro behaves like if, except that it accepts (if=> test => procedure) syntax, in which procedure is called with the value of test if it is not false. The symbol => must match hygienically, that is, it must refer to the same binding as in the macro definition.

(define-syntax if=>
  (er-macro-transformer
    (^[form rename id=?]
      (match form
        [(_ test a b)
         (if (id=? (rename '=>) a)
           (quasirename rename
             (let ((t ,test))
               (if t (,b t))))
           (quasirename rename
             (if ,test ,a ,b)))]))))

The call (rename '=>) returns an identifier that captures the binding of => in the macro definition, and using id=? with the thing passed to the macro argument checks if both refer to the same binding.

(if=> 3 => list)  ⇒ (3)
(if=> #f => list) ⇒ #<undef>

;; If the second argument isn't =>, if=> behaves like ordinary if:
(if=> #t 1 2)     ⇒ 1

;; The binding of => in macro use environment differs from
;; the macro definition environment, so this if=> behaves like
;; ordinary if, instead of recognizing literal =>.
(let ((=> 'oof)) (if=> 3 => list)) ⇒ oof
Macro: quasirename renamer form

It works like quasiquote, except that the symbols and identifiers that appear in the “literal” portion of form (i.e. outside of unquote and unquote-splicing) are replaced by the result of applying rename on themselves.

For example, a form:

(quasirename r (a ,b c "d"))

would be equivalent to write:

(list (r 'a) b (r 'c) "d")

This is not specifically tied to macros; the renamer can be any procedure that takes one symbol or identifier argument:

(quasirename (^[x] (symbol-append 'x: x)) (+ a ,(+ 1 2) 5))
  ⇒ (x:+ x:a 3 5)

However, it comes pretty handy to construct the result form in ER macros. Compare the following two:

(use util.match)

;; using quasirename
(define-syntax swap
  (er-macro-transformer
    (^[f r c]
      (match f
        [(_ a b) (quasirename r
                   (let ((tmp ,a))
                     (set! ,a ,b)
                     (set! ,b tmp)))]))))

;; not using quasirename
(define-syntax swap
  (er-macro-transformer
    (^[f r c]
      (match f
        [(_ a b) `((r'let) (((r'tmp) ,a))
                     ((r'set!) ,a ,b)
                     ((r'set!) ,b (r'tmp)))]))))

Previous: , Up: Hygienic macros   [Contents][Index]