Macro
So far we've been able to define our own procedures in Scheme using the define
special form. When we call these procedures, we have to follow the rules for evaluating call expressions, which involve evaluating all the operands.
We know that special form expressions do not follow the evaluation rules of call expressions. Instead, each special form has its own rules of evaluation, which may include not evaluating all the operands. Wouldn't it be cool if we could define our own special forms where we decide which operands are evaluated? Consider the following example where we attempt to write a function that evaluates a given expression twice:
scm> (define (twice f) (begin f f))
twice
scm> (twice (print 'woof))
woof
Since twice
is a regular procedure, a call to twice
will follow the same rules of evaluation as regular call expressions; first we evaluate the operator and then we evaluate the operands. That means that woof
was printed when we evaluated the operand (print 'woof)
. Inside the body of twice
, the name f
is bound to the value undefined
, so the expression (begin f f)
does nothing at all!
The problem here is clear: we need to prevent the given expression from evaluating until we're inside the body of the procedure. This is where the define-macro
special form, which has identical syntax to the regular define
form, comes in:
scm> (define-macro (twice f) (list 'begin f f))
twice
define-macro
allows us to define what's known as a macro
, which is simply a way for us to combine unevaluated input expressions together into another expression. When we call macros, the operands are not evaluated, but rather are treated as Scheme data. This means that any operands that are call expressions or special form expression are treated like lists.
If we call (twice (print 'woof))
, f
will actually be bound to the list (print 'woof)
instead of the value undefined
. Inside the body of define-macro
, we can insert these expressions into a larger Scheme expression. In our case, we would want a begin
expression that looks like the following:
(begin (print 'woof) (print 'woof))
As Scheme data, this expression is really just a list containing three elements: begin
and (print 'woof)
twice, which is exactly what (list 'begin f f)
returns. Now, when we call twice
, this list is evaluated as an expression and (print 'woof)
is evaluated twice.
scm> (twice (print 'woof))
woof
woof
To recap, macros are called similarly to regular procedures, but the rules for evaluating them are different. We evaluated lambda procedures in the following way:
- Evaluate operator
- Evaluate operands
- Apply operator to operands, evaluating the body of the procedure
However, the rules for evaluating calls to macro procedures are:
- Evaluate operator
- Apply operator to unevaluated operands
- Evaluate the expression returned by the macro in the frame it was called in.