Dynamic Scope and Eval


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…

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.

Updated 2023-03-16