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.)