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.
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)
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)
eval
functionSince 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:
scheme-report-environment
: takes an integer version (must be 5 for R5RS) and returns an environment that is empty except for the built-ins defined in the standard.null-environment
: takes an integer version (must be 5 for R5RS) and returns an environment that is empty except for special forms (e.g., cond
).interaction-environment
: takes no argument, and returns some implementation-defined environment.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
let*->nested-let
that, given a list representation of a Scheme program, transforms all instances of let*
expressions into nested sequences of let
expressions.let->lambda
, given a list representation of a Scheme program, transforms all its let
expressions into applications of lambda
expressions.my-eval
function that evaluates some "interesting" subset of Scheme. Suggestion for a subset: cond
lambda
define
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.)
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.)