You've noticed this pattern before.
x = x + 5
or in lisp
(setq x (+ x 5))
Many languages abstract it away with fancy syntax.
x += 5;
I've even heard that some allow using any function with that syntax.
f(x, y) = x * y + 3;
x f= true;
Here's an equivalent syntax in lisp:
(to x (+ 5)) ; Expands to (setq x (+ x 5))
;; You can pass the function multiple arguments too.
(to x (+ y 5)) ; Expands to (setq x (+ x y 5))
This is the macro definition in Emacs Lisp.
(defmacro to (var form)
(let ((func (car form))
(args (cdr form)))
`(setq ,var (,func ,var ,@args))))
Emacs Lisp has two primary types of functions: functions and macros. Lisp 1.5 had another sort of function
called an fexpr that acts similar to a macro. While macros are called at compile time and expand into code that
is evaluated at runtime, fexprs are called at runtime and must explicitly call eval
to return the
same result as an equivalent macro. When an fexpr is called, each argument is passed as a quoted form, so
if (+ 1 2 3)
was an argument, the literal code (+ 1 2 3)
would be passed to the
fexpr. The fexpr may then call eval
to return the sum.
Emacs Lisp doesn't have fexprs, but we can emulate them by quoting every argument passed to a function.
The remainder of the code will only work in Emacs Lisp, and only if dynamic binding is enabled.
;; Define a normal function, but act as if the argument is passed as a quoted form.
(defun fake-fexpr (arg)
(eval arg))
;; Simulate an fexpr call by quoting the argument.
(fake-fexpr `(+ 1 2 3)) ; ⇒ 6
And we're done. That's all an fexpr is. Now let's see why it's useful.
Since dynamic scope is enabled, eval
has the ability to create variables that are accessible
outside of the scope they were defined.
(defun inject (x)
(eval `(setq ,x t)))
(inject 'y)
y ; ⇒ t
Like macros, fexprs used with dynamic scope…
- receive arguments as quoted forms
- can transform and evaluate code
- can inject variables into the calling scope
In other words, fexprs can do everything macros can.
Let's define the to
macro using a fake fexpr.
(defun to (var form)
(set var (eval (list* (car form) var (cdr form)))))
(let ((x 3))
(to 'x '(+ 5))
x) ; ⇒ 8
As it turns out, macros are just a pattern of fexpr and eval
usage:
;; This fexpr definition syntax is made up, but it should get the point across.
(defexpr macro-name (&rest args)
(eval …))
We can even use the macro pattern to define defmacro
as an fexpr that defines fexprs.
(defexpr defmacro (name parameters &rest body)
(eval `(defexpr ,name ,parameters
(eval (progn
,@body)))))
Disclaimer: I have done virtually no research on this! Take the following opinions with a grain of salt.
The lisp metacircular evaluator seems to be a popular topic among lispers. I've never seen the practicality
of eval
, even without macros, so the popularity of the metacircular evaluator confused me. This is
especially the case for Scheme where lexical scope is the only option, which means that there is no way
for eval
to define variables that leak into the caller's scope. Obviously it's pretty cool to be
able to implement a programming language in itself on a single page, but I think the combination of dynamic
scope and eval
to create fexpr-like functions is a more appropriate reason for it to be hailed as
the "root of lisp". Once you have quote
and an eval
that can manipulate the global
environment, you effectively have macros.