Scheme Macros for Common Lisp

[Download mbe.lsp]

Dorai Sitaram



mbe.lsp defines for Common Lisp the macro definers define-syntax, let-syntax, and letrec-syntax, as described in the Scheme report R5RS [3].

These macro definers, also called macro by example (MBE), use simple patterns, including ellipsis, to specify how a macro should be expanded. They were propounded by Eugene Kohlbecker [12] in the mid-1980s and became part of the Scheme standard in 1991.

MBE contrast dramatically with Common Lisp's defmacro and macrolet, which require the user to program expansions using extensive list destructuring and restructuring, often involving several nestings of backquotes, quotes, unquotes and spliced unquotes.

For example, a let macro could be defined using MBE as follows (we will use my-let in order not to clash with the standard let):

(define-syntax my-let
  (syntax-rules ()
    ((my-let ((x v) ***) e ***)
     ((lambda (x ***) e ***) v ***))))

The *** indicates an ellipsis. Thus, the input pattern ((x v) ***) matches a list, each of whose elements match (x v). When expanding, the output pattern (x ***) represents a list containing all the x's in the same order that they matched the x's in the input pattern.1

Using defmacro, the definition would be:

(defmacro my-let (xvxv &rest ee)
  `((lambda ,(mapcar #'car xvxv)
      ,@ee)
    ,@(mapcar #'cadr xvxv)))

1  define-syntax

Global MBE macros are defined using the form define-syntax. Its first subexpression is the name of the macro to be defined, and its second subexpression is the syntax-rules governing that macro's expansion.

The first subexpression of syntax-rules is a list of auxiliary keywords pertaining to the macro. The remaining subexpressions of syntax-rules are the macro expansion clauses. Each clause consists of an in-pattern and an out-pattern. If the macro call matches an in-pattern, it expands to the corresponding out-pattern. The clauses are tried in sequence: a macro call expands based on the first in-pattern that it matches.

Here is an example of a disjunction form my-or (same as CL or):

(define-syntax my-or
  (syntax-rules ()
    ((my-or) nil)
    ((my-or arg1) arg1)
    ((my-or arg1 arg2) (let ((temp arg1))
                         (if temp temp arg2)))
    ((my-or arg1 arg2 arg3 ***) (my-or (my-or arg1 arg2) arg3 ***))))

1.1  Hygiene

There is one problem with the definition of my-or above. If you have a global variable temp bound to t, then the following expression

(my-or nil temp)

does not give the expected result (t). This is because

(my-or nil temp)

expands to

(let ((temp nil))
  (if temp temp temp))

which evaluates to nil. The temporary lexical variable temp introduced in the macro expansion shadows (or captures) the global temp and therefore causes an erroneous result.

The Scheme report requires that the macro definers be hygienic, i.e., that they automatically avoid these lexical captures. This Common Lisp implementation does not provide hygiene. There are two ways out:

1. Use unusual names for any lexical variables you introduce in the expansion pattern, e.g.,

...
((my-or arg1 arg2) (let ((__#temp#__ arg1))
                     (if __#temp#__ __#temp#__ arg2)))
...

and then hope that nobody ever uses __#temp#__.

2. A better alternative is to use a generated symbol that is guaranteed not to clash with anything Lisp or you can come up with before or after. To introduce this gensym, mbe.lsp provides a with-wrapper for the expansion pattern. E.g.,

...
((my-or arg1 arg2) (with ((temp (gensym)))
                     (let ((temp arg1))
                       (if temp temp arg2))))
...

Here, the temp identifier used in the expansion pattern will be a gensym, and not the symbol literally named temp.

2  let-syntax and letrec-syntax

define-syntax defines globally visible macros. For local macros (cf. Common Lisp's macrolet), use let-syntax or letrec-syntax. Example:

(let-syntax ((either (syntax-rules ()
                       ((either x y) (with ((tmp (gensym)))
                                       (let ((tmp x))
                                         (if tmp tmp y)))))))
  (either "this" "that"))

The either macro is local to the let-syntax body, and will not be visible outside.

The Scheme report distinguishes between let-syntax and letrec-syntax the same way that Common Lisp distinguishes between flet and labels. However, this distinction is not well preserved in mbe.lsp because these forms are defined using macrolet, and macrolet doesn't have the same scoping style.

3  References

[1]  

Eugene E. Kohlbecker, Jr., Daniel P. Friedman, Matthias Felleisen, and Bruce Duba, ``Hygienic Macro Expansion'', in Proc. 1986 ACM Conf. on Lisp and Functional Programming, pp. 151-161.

[2]  

Eugene E. Kohlbecker, Jr., Syntactic Extensions in the Programming Language Lisp, PhD thesis, Indiana Univ., 1986.

[3]  

Revised (5) Report on the Algorithmic Language Scheme (``R5RS''), eds. Richard Kelsey, William Clinger, and Jonathan Rees, 1998.


1 The ellipsis is represented by `...' in Scheme. This Common Lisp implementation uses `***' instead, since `...' is disallowed by Common Lisp's identifier conventions.

Last modified: Mon, Feb 25, 2002, 4:02 pm US/Eastern
HTML conversion by TeX2page 4p4h