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 [1, 2] 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)))
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 ***))))
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.
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.
| [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.