CSE 341: Scheme: Quote, Quasiquote, and Metaprogramming

Scheme has a convenient syntax for representing data literals: prefix any expression with ' (single quote) and the expression, rather than being evaluated, will be returned as data:

'3        ; => 3                 (a number)
'"hi"     ; => "hi"              (a string)
'a        ; => a                 (a symbol)
'(+ 3 4)  ; => (list '+ '3 '4)   (a list)
'(a b c)  ; => (list 'a 'b 'c)   (a list)

'(define x 25)                   (a list)
          ; => (list 'define 'x '25)
          ; => (list 'define 'x 25)

'(lambda (x) (+ x 3))            (a list)
          ; => (list 'lambda (list 'x) (list '+ 'x '3))
          ; => (list 'lambda (list 'x) (list '+ 'x 3))

As these examples illustrate, "quoted" data remains unevaluated, and provides a convenient way of representing Scheme programs. This is one of the big payoffs of Lisp's simple syntax: since programs themselves are lists, it is extremely simple to represent Lisp programs as data. Compare the simplicity of quoted lists with the ML datatype that we used to represent ML expressions.

This makes it simple to write programs that manipulate other programs --- it is easy to construct and transform programs on the fly.

Note that names in Lisp programs are translated into symbols in quoted Lisp expressions. This is so that quoted names can be distinguished from quoted strings; consider the difference between the following two expressions:

'(define x 10)    ; => (list 'define 'x 10)
'("define" x 10)  ; => (list "define" 'x 10)

This distinction is probably the only good reason that strings and symbols are distinct data types in Lisp.

Quasiquote

Sometimes one doesn't want to escape evaluation of an entire list. In this case, one can use the ` (backquote) operator, also called quasiquote with the , (comma) operator, also called unquote. Quasiquote behaves like quote, except that unquoted expressions are evaluated:

`(1 2 ,(+ 3 4))   ; => '(1 2 7)

Special forms: quote, quasiquote, unquote

The quote special form behaves like ' applied to its argument:

(quote (1 2 (+ 3 4)))   ; => '(1 2 (+ 3 4))

The quasiquote and unquote special forms behaves like ` and , respectively:

(quasiquote (1 2 (unquote (+ 3 4))))  ; => '(1 2 7)

The eval function

Since we have a method for representing programs as data, it is convenient to have a function that evaluates that data as if it were a part of the currently running program. Indeed, Scheme has such a function eval, but its behavior is not well-standardized across Scheme implementations. Here is an example that works in DrScheme:

(define x 5)
(define y '(+ x 10))
(eval y)             ; => 15

(eval '((lambda (x y) (string-concat x y)) "foo" "bar"))
                     ; => "foobar"

By default, DrScheme's eval evaluates code in the top-level environment, not the environment at the point that eval is called:

(define a 10)
(define b '(lambda (x) (+ x a)))
(let* ((a 20))
  (eval b))

R5RS states that eval must take an environment as a parameter; environments may be obtained from the following functions:

In practice, these environments are not very useful, particularly null-environment:

(eval '(+ a 5) (scheme-report-environment 5))
         ; => reference to undefined identifier: a

(eval '(+ 1 2) (null-environment 5))
         ; => reference to undefined identifier: +

Implementations are free to define their own extensions for obtaining other environments. DrScheme calls environments "namespaces"; you can constructa fresh namespace using make-namespace, and execute eval in a new namespace by using parameterize:

(define ns (make-namespace))
(parameterize ((current-namespace ns))
  (eval '(define k 10))                   ; defines a name in in ns
  (eval '(+ k 4)))                        ; evals '(+ k 4) in ns

Suggested exercises

  1. Write a function let*->nested-let that, given a list representation of a Scheme program, transforms all instances of let* expressions into nested sequences of let expressions.
  2. Write a functon that let->lambda, given a list representation of a Scheme program, transforms all its let expressions into applications of lambda expressions.
  3. Test the prior two functions on some quoted Scheme expressions that represent programs.
  4. Write a my-eval function that evaluates some "interesting" subset of Scheme. Suggestion for a subset:

    Use some sensible representation (e.g., a list of symbol-value pairs) for environments. (Notice that you can use let*->nested-let and let-lambda to make the above evaluator handle a larger subset of Scheme.)

  5. Write a function linearize-let that, given a list representation of a Scheme program, transforms a let expression with multiple name bindings into a "normal form" with only one name bound per let. (Note: harder than it seems at first glance! To ensure that you don't shadow outer bindings, you must rename each of the bindings, then rename the free variables in the body expression(s) consistently.)