(Note: Whether or not Lisp code is compiled or interpreted is implementation-dependent. In this article I assume that your implementation compiles it, but also provides a way of switching between the two. If you want to reproduce the exact same results you will need to use South Bank Common Lisp. You have to figure out on your own if your implementation does support both methods and how to do the switch. You should be able to get help in the man pages, the info pages, paper manuals, or IRC channels for public discussion about Lisp.)
When Lisp code is read it`s internally stored as a Lisp data structure: a linked series of conses (generic pairs), simply known as a list. For example, if you type:
(+ 2 3)
at the REPL (read-eval-print loop), the reader turns that sequence of characters into the following tree (lists of lists of lists... you get the idea) of symbols:
(cons '+ (cons 2 (cons 3 nil)))
and the Lisp system then analyzes that expression and tries to evaluate it (by compiling and executing or interpreting). I'm sure you're familiar with that already. The fact that Lisp code is also Lisp data makes Lisp unique amongst programming languages. You can use functions written for processing Lisp lists and pass to them lists or trees of Lisp code.
You can create code by any means imaginable:
* (list '+ 2 3)
(+ 2 3)
* (defun my-expr (symbol) (list symbol 2 3))
MY-EXPR
* (my-expr '+)
(+ 2 3)
You can store code in top-level variables (or any data structure, really):
* (defvar *code* '(+ 2 3))
*CODE*
and then ask Lisp to evaluate it at run-time:
* *code*
(+ 2 3)
* (eval *code*)
5or even modify it:
* (rplacd *code* '(5 7))
(+ 5 7)
* (eval *code*)
12
Since evaluation of code at run-time can pose security risks (you can slip in calls to malicious code and other shenanigans) and can also be costly in terms of CPU time, it`s frowned upon by many but that shouldn't stop us though from using it at appropriate times.
Common Lisp provides a Macro facility taking the idea of transforming code (creation, modification, etc.) at run-time to just past read-time. So called Macro forms look like function calls, but instead of values compute code. For example:
* (defmacro code () *code*)
CODE
* (code)
12
To aid programmers in writing macros CL provides us with the functions macroexpand-1 and macroexpand. Both are called with a quoted macro form:
* (macroexpand-1 '(code))
(+ 5 7)
T
* (macroexpand '(code))
(+ 5 7)
T
The former expands the macro form once but not any further (helpful when we need to debug macros that contain macro form themselves) while the latter does a full expansion of the macro form.
The example above is simple enough that both expansion end up being the same. But what did happen in the expansion? The macro expanded to the list
(+ 5 7)(stored in the variable
*CODE*) that then was evaluated by Lisp. Let`s make clear what is happening by wrapping the macro form in a function:
* (defun run-code () (code))
RUN-CODE
* (run-code)
12
* (code)
12
So far so good. As expected, both the expansion of the macro form
(code)and the return value of the function call
(run-code)result in the number
12. Now let`s hit home the fact that macro expansion happens at a time different from run-time in compiled Lisp:
* (rplacd *code* '(10 10))
(+ 10 10)
* (code)
20
* (run-code)
12
The result of the expansion of the macro form has changed but the result of the function call hasn`t, because the macro form was expanded when the function was compiled. What the compiler really sees after the expansion of the macro form is:
(defun my-code () (+ 5 7))
So changing the value of
*CODE*after the expansion will not change already the expanded macro forms in other code. It`s a completely different story when the code is interpreted by Lisp:
* sb-ext:*evaluator-mode*
:COMPILE
* (setf sb-ext:*evaluator-mode* :interpret)
:INTERPRET
* (setf *code* '(+ 5 7))
(+ 5 7)
* (defun run-code () (code))
RUN-CODE
* (code)
12
* (run-code)
12
* (rplacd *code* '(10 10))
(+ 10 10)
* (code)
20
* (run-code)
20
Here the results are the same after all, because the macro form is expanded and evaluated at run time, not at compile time. I'm going to go out on a limb and say that your macros should not depend on side-effects like this. Using a global variable to control the expansion of a macro form is bad style and wreaks havoc with side effect-free debugging.
Macros allow you: to introduce new operators into the language which helps you to write compact programs (syntactic abstraction); to write your own little language on-top of Lisp which is automatically compiled by the Lisp environment (domain-specific languages); to reuse code by letting programs write programs for you (generic or meta programming)... and on and on and on. The possibilities are endless and up to your imagination.
| History | ||
| When | What | Who |
| 07/03/08 | Released ver.1 of article | Chris Eineke |