QUASIQUOTE
is an extension of the QUOTE
special form which is convenient for writing macros.
For symbols and other simple data, QUASIQUOTE
behaves
like QUOTE
, returning the datum unevaluated. Lists
are also return without being evaluated, with two exceptions. If
an element of the list (or a sub-list) is of the form
(UNQUOTE expr)
, then expr
is
evaluated and the result inserted into the list in place.
(UNQUOTE-SPLICING expr)
is similar, but the
result of evaluating expr
must be a list, the items
of which are spliced into the parent list.
(QUASIQUOTE (+ 1 (UNQUOTE (+ 2 3))))evaluates to
(+ 1 5)
If we define L
to be the list (3 4 5)
then
(QUASIQUOTE (1 2 (UNQUOTE-SPLICING L)))evaluates to
(1 2 3 4 5)
Just like QUOTE
, we will define the following
abbreviations.
Abbreviation | Equivalent to |
---|---|
`expr |
(QUASIQUOTE expr) |
,expr |
(UNQUOTE expr) |
,@expr |
(UNQUOTE-SPLICING expr) |
Rewriting the examples above with this syntax gives
`(+ 1 ,(+ 2 3))and
`(1 2 ,@L)
We extend the lexer to understand the additional special tokens.
int lex(const char *str, const char **start, const char **end) { const char *ws = " \t\n"; const char *delim = "() \t\n"; const char *prefix = "()\'`"; str += strspn(str, ws); if (str[0] == '\0') { *start = *end = NULL; return Error_Syntax; } *start = str; if (strchr(prefix, str[0]) != NULL) *end = str + 1; else if (str[0] == ',') *end = str + (str[1] == '@' ? 2 : 1); else *end = str + strcspn(str, delim); return Error_OK; }
read_expr
must expand the abbreviations in the same
way as QUOTE
int read_expr(const char *input, const char **end, Atom *result) { . . . if (token[0] == '(') { . . . } else if (token[0] == '`') { *result = cons(make_sym("QUASIQUOTE"), cons(nil, nil)); return read_expr(*end, end, &car(cdr(*result))); } else if (token[0] == ',') { *result = cons(make_sym( token[1] == '@' ? "UNQUOTE-SPLICING" : "UNQUOTE"), cons(nil, nil)); return read_expr(*end, end, &car(cdr(*result))); } else { . . . } }
The QUASIQUOTE
operator itself may be defined as a macro.
First we need a few helper functions.
(define (append a b) (foldr cons b a)) (define (caar x) (car (car x))) (define (cadr x) (car (cdr x)))
(append a b)
concatenates the lists a
and b
.
And now the macro itself:
(defmacro (quasiquote x) (if (pair? x) (if (eq? (car x) 'unquote) (cadr x) (if (eq? (if (pair? (car x)) (caar x) nil) 'unquote-splicing) (list 'append (cadr (car x)) (list 'quasiquote (cdr x))) (list 'cons (list 'quasiquote (car x)) (list 'quasiquote (cdr x))))) (list 'quote x)))
The definition above is a little hard to follow, since the
resulting expression must be built up using LIST
and may include additional calls to QUASIQUOTE
.
Quasiquotation allows us to make the body of a macro look like
the expression it returns; for example the IGNORE
macro in chapter 11
(DEFMACRO (IGNORE X) (CONS 'QUOTE (CONS X NIL)))can now be written
(DEFMACRO (IGNORE X) `(QUOTE ,X))and the operation is made clear.
> `(+ 1 ,(+ 2 3)) (+ 1 5) > (define l '(3 4 5)) L > `(1 2 ,@l) (1 2 3 4 5)
let
We will now use QUASIQUOTE
to define a new special
form:
(LET ((sym1 expr1) (sym2 expr2) ...) body...)
LET
causes the expressions expr
to be evaluated
with the symbols sym1
, sym2
... bound to the
result of evaluating expr1
, expr2
and so on.
The result of the last expression body
to be evaluated
is returned.
The definition is simple.
(defmacro (let defs . body) `((lambda ,(map car defs) ,@body) ,@(map cadr defs)))
When we evaluate the form
(LET ((X 3) (Y 5)) (+ X Y))it is transformed by the
LET
macro into
((LAMBDA (X Y) (+ X Y)) 3 5)which behaves as desired.
> (let ((x 3) (y 5)) (+ x y)) 8 > x Symbol not bound
The LET
expression clarifies the programmer's
intent to make temporary definitions.
We can use LET
to extend the built-in binary operator
+
to accept any number of arguments.
(define + (let ((old+ +)) (lambda xs (foldl old+ 0 xs))))
Compare this with the definition of ADD
add the end
of chapter 10.
> (+ 1 2 3 4) 10
We didn't have to touch builtin_add
or even recompile
the interpreter.