524 lines
18 KiB
Racket
524 lines
18 KiB
Racket
#lang scribble/doc
|
|
@(require scribble/manual
|
|
scribble/eval
|
|
racket/class
|
|
"guide-utils.ss")
|
|
|
|
@title[#:tag "reflection" #:style 'toc]{Reflection and Dynamic Evaluation}
|
|
|
|
Racket is a @italic{dynamic} language. It offers numerous facilities
|
|
for loading, compiling, and even constructing new code at run
|
|
time.
|
|
|
|
@local-table-of-contents[]
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
@section[#:tag "eval"]{@racket[eval]}
|
|
|
|
@margin-note{This example will not run properly in DrRacket's definitions window
|
|
but it will in the interactions window; read on for
|
|
why (specifically see the end of @secref["namespaces"]).}
|
|
|
|
The @racket[eval] function takes a ``quoted'' expression or definition
|
|
and evaluates it:
|
|
|
|
@interaction[
|
|
(eval '(+ 1 2))
|
|
]
|
|
|
|
The power of @racket[eval] is that an expression can be
|
|
constructed dynamically:
|
|
|
|
@interaction[
|
|
(define (eval-formula formula)
|
|
(eval `(let ([x 2]
|
|
[y 3])
|
|
,formula)))
|
|
(eval-formula '(+ x y))
|
|
(eval-formula '(+ (* x y) y))
|
|
]
|
|
|
|
Of course, if we just wanted to evaluate expressions with given values
|
|
for @racket[x] and @racket[y], we do not need @racket[eval]. A more
|
|
direct approach is to use first-class functions:
|
|
|
|
@interaction[
|
|
(define (apply-formula formula-proc)
|
|
(formula-proc 2 3))
|
|
(apply-formula (lambda (x y) (+ x y)))
|
|
(apply-formula (lambda (x y) (+ (* x y) y)))
|
|
]
|
|
|
|
However, if expressions like @racket[(+ x y)] and @racket[(+ (* x y)
|
|
y)] are read from a file supplied by a user, for example, then
|
|
@racket[eval] might be appropriate. Similarly, the @tech{REPL} reads
|
|
expressions that are typed by a user and uses @racket[eval] to
|
|
evaluate them.
|
|
|
|
Also, @racket[eval] is often used directly or indirectly on whole
|
|
modules. For example, a program might load a module on demand using
|
|
@racket[dynamic-require], which is essentially a wrapper around
|
|
@racket[eval] to dynamically load the module code.
|
|
|
|
@; ----------------------------------------
|
|
|
|
@subsection{Local Scopes}
|
|
|
|
The @racket[eval] function cannot see local bindings in the context
|
|
where it is called. For example, calling @racket[eval] inside an
|
|
unquoted @racket[let] form to evaluate a formula does not make values
|
|
visible for @racket[x] and @racket[y]:
|
|
|
|
@interaction[
|
|
(define (broken-eval-formula formula)
|
|
(let ([x 2]
|
|
[y 3])
|
|
(eval formula)))
|
|
(broken-eval-formula '(+ x y))
|
|
]
|
|
|
|
The @racket[eval] function cannot see the @racket[x] and @racket[y]
|
|
bindings precisely because it is a function, and Racket is a lexically
|
|
scoped language. Imagine if @racket[eval] were implemented as
|
|
|
|
@racketblock[
|
|
(define (eval x)
|
|
(eval-expanded (macro-expand x)))
|
|
]
|
|
|
|
then at the point when @racket[eval-expanded] is called, the most
|
|
recent binding of @racket[x] is to the expression to evaluate, not the
|
|
@racket[let] binding in @racket[broken-eval-formula]. Lexical scope
|
|
prevents such confusing and fragile behavior, and consequently
|
|
prevents @racket[eval] from seeing local bindings in the context where
|
|
it is called.
|
|
|
|
You might imagine that even though @racket[eval] cannot see the local
|
|
bindings in @racket[broken-eval-formula], there must actually be a
|
|
data structure mapping @racket[x] to @racket[2] and @racket[y] to
|
|
@racket[3], and you would like a way to get that data structure. In
|
|
fact, no such data structure exists; the compiler is free to replace
|
|
every use of @racket[x] with @racket[2] at compile time, so that the
|
|
local binding of @racket[x] does not exist in any concrete sense at
|
|
run-time. Even when variables cannot be eliminated by
|
|
constant-folding, normally the names of the variables can be
|
|
eliminated, and the data structures that hold local values do not
|
|
resemble a mapping from names to values.
|
|
|
|
@; ----------------------------------------
|
|
|
|
@subsection[#:tag "namespaces"]{Namespaces}
|
|
|
|
Since @racket[eval] cannot see the bindings from the context where it
|
|
is called, another mechanism is needed to determine dynamically
|
|
available bindings. A @deftech{namespace} is a first-class value that
|
|
encapsulates the bindings available for dynamic evaluation.
|
|
|
|
@margin-note{Informally, the term @defterm{namespace} is sometimes
|
|
used interchangeably with @defterm{environment} or
|
|
@defterm{scope}. In Racket, the term @defterm{namespace} has the
|
|
more specific, dynamic meaning given above, and it should not be
|
|
confused with static lexical concepts.}
|
|
|
|
Some functions, such as @racket[eval], accept an optional namespace
|
|
argument. More often, the namespace used by a dynamic operation is the
|
|
@deftech{current namespace} as determined by the
|
|
@racket[current-namespace] @tech{parameter}.
|
|
|
|
When @racket[eval] is used in a @tech{REPL}, the current namespace is the one
|
|
that the @tech{REPL} uses for evaluating expressions. That's why the
|
|
following interaction successfully accesses @racket[x] via
|
|
@racket[eval]:
|
|
|
|
@interaction[
|
|
(define x 3)
|
|
(eval 'x)
|
|
]
|
|
|
|
In contrast, try the following a simple module and running in directly
|
|
in DrRacket or supplying the file as a command-line argument to
|
|
@exec{racket}:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(eval '(cons 1 2))
|
|
]
|
|
|
|
This fails because the initial current namespace is empty. When you
|
|
run @exec{racket} in interactive mode (see
|
|
@secref["start-interactive-mode"]), the initial namespace is
|
|
initialized with the exports of the @racket[racket] module, but when
|
|
you run a module directly, the initial namespace starts empty.
|
|
|
|
In general, it's a bad idea to use @racket[eval] with whatever
|
|
namespace happens to be installed. Instead, create a namespace
|
|
explicitly and install it for the call to eval:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(define ns (make-base-namespace))
|
|
(eval '(cons 1 2) ns) (code:comment @#,t{works})
|
|
]
|
|
|
|
The @racket[make-base-namespace] function creates a namespace that is
|
|
initialized with the exports of @racket[racket/base]. The later
|
|
section @secref["mk-namespace"] provides more information on creating
|
|
and configuring namespaces.
|
|
|
|
@; ----------------------------------------
|
|
|
|
@subsection{Namespaces and Modules}
|
|
|
|
As with @racket[let] bindings, lexical scope means that @racket[eval]
|
|
cannot automatically see the definitions of a @racket[module] in which
|
|
it is called. Unlike @racket[let] bindings, however, Racket provides a
|
|
way to reflect a module into a @tech{namespace}.
|
|
|
|
The @racket[module->namespace] function takes a quoted @tech{module
|
|
path} and produces a namespace for evaluating expressions and
|
|
definitions as if they appeared in the @racket[module] body:
|
|
|
|
@interaction[
|
|
(module m racket/base
|
|
(define x 11))
|
|
(require 'm)
|
|
(define ns (module->namespace ''m))
|
|
(eval 'x ns)
|
|
]
|
|
|
|
@margin-note{The double quoting in @racket[''m] is because @racket['m]
|
|
is a module path that refers to an interactively declared module, and
|
|
so @racket[''m] is the quoted form of the path.}
|
|
|
|
The @racket[module->namespace] function is mostly useful from outside
|
|
a module, where the module's full name is known. Inside a
|
|
@racket[module] form, however, the full name of a module may not be
|
|
known, because it may depend on where the module source is location
|
|
when it is eventually loaded.
|
|
|
|
From within a @racket[module], use @racket[define-namespace-anchor] to
|
|
declare a reflection hook on the module, and use
|
|
@racket[namespace-anchor->namespace] to reel in the module's
|
|
namespace:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(define-namespace-anchor a)
|
|
(define ns (namespace-anchor->namespace a))
|
|
|
|
(define x 1)
|
|
(define y 2)
|
|
|
|
(eval '(cons x y) ns) (code:comment @#,t{produces @racketresult[(1 . 2)]})
|
|
]
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
@section[#:tag "mk-namespace"]{Manipulating Namespaces}
|
|
|
|
A @tech{namespace} encapsulates two pieces of information:
|
|
|
|
@itemize[
|
|
|
|
@item{A mapping from identifiers to bindings. For example, a
|
|
namespace might map the identifier @racketidfont{lambda} to the
|
|
@racket[lambda] form. An ``empty'' namespace is one that maps
|
|
every identifier to an uninitialized top-level variable.}
|
|
|
|
@item{A mapping from module names to module declarations and
|
|
instances.}
|
|
|
|
]
|
|
|
|
The first mapping is used for evaluating expressions in a top-level
|
|
context, as in @racket[(eval '(lambda (x) (+ x 1)))]. The second
|
|
mapping is used, for example, by @racket[dynamic-require] to locate a
|
|
module. The call @racket[(eval '(require racket/base))] normally uses
|
|
both pieces: the identifier mapping determines the binding of
|
|
@racketidfont{require}; if it turns out to mean @racket[require], then
|
|
the module mapping is used to locate the @racketmodname[racket/base]
|
|
module.
|
|
|
|
From the perspective of the core Racket run-time system, all
|
|
evaluation is reflective. Execution starts with an initial namespace
|
|
that contains a few primitive modules, and that is further populated
|
|
by loading files and modules as specified on the command line or as
|
|
supplied in the @tech{REPL}. Top-level @racket[require] and
|
|
@racket[define] forms adjusts the identifier mapping, and module
|
|
declarations (typically loaded on demand for a @racket[require] form)
|
|
adjust the module mapping.
|
|
|
|
@; ----------------------------------------
|
|
|
|
@subsection{Creating and Installing Namespaces}
|
|
|
|
The function @racket[make-empty-namespace] creates a new, empty
|
|
@tech{namespace}. Since the namespace is truly empty, it cannot at
|
|
first be used to evaluate any top-level expression---not even
|
|
@racket[(require racket)]. In particular,
|
|
|
|
@racketblock[
|
|
(parameterize ([current-namespace (make-empty-namespace)])
|
|
(namespace-require 'racket))
|
|
]
|
|
|
|
fails, because the namespace does not include the primitive modules on
|
|
which @racket[racket] is built.
|
|
|
|
To make a namespace useful, some modules must be @deftech{attached}
|
|
from an existing namespace. Attaching a module adjusts the mapping of
|
|
module names to instances by transitively copying entries (the module
|
|
and all its imports) from an existing namespace's mapping. Normally,
|
|
instead of just attaching the primitive modules---whose names and
|
|
organization are subject to change---a higher-level module is
|
|
attached, such as @racketmodname[racket] or
|
|
@racketmodname[racket/base].
|
|
|
|
The @racket[make-base-empty-namespace] function provides a namespace
|
|
that is empty, except that @racketmodname[racket/base] is
|
|
attached. The resulting namespace is still ``empty'' in the sense that
|
|
the identifiers-to-bindings part of the namespace has no mappings;
|
|
only the module mapping has been populated. Nevertheless, with an
|
|
initial module mapping, further modules can be loaded.
|
|
|
|
A namespace created with @racket[make-base-empty-namespace] is
|
|
suitable for many basic dynamic tasks. For example, suppose that a
|
|
@racketmodfont{my-dsl} library implements a domain-specific language
|
|
in which you want to execute commands from a user-specified file. A
|
|
namespace created with @racket[make-base-empty-namespace] is enough to
|
|
get started:
|
|
|
|
@racketblock[
|
|
(define (run-dsl file)
|
|
(parameterize ([current-namespace (make-base-empty-namespace)])
|
|
(namespace-require 'my-dsl)
|
|
(load file)))
|
|
]
|
|
|
|
Note that the @racket[parameterize] of @racket[current-namespace] does
|
|
not affect the meaning of identifiers like @racket[namespace-require]
|
|
within the @racket[parameterize] body. Those identifiers obtain their
|
|
meaning from the enclosing context (probably a module). Only
|
|
expressions that are dynamic with respect to this code, such as the
|
|
content of @racket[load]ed files, are affected by the
|
|
@racket[parameterize].
|
|
|
|
Another subtle point in the above example is the use of
|
|
@racket[(namespace-require 'my-dsl)] instead of @racket[(eval
|
|
'(require my-dsl))]. The latter would not work, because @racket[eval]
|
|
needs to obtain a meaning for @racket[require] in the namespace, and
|
|
the namespace's identifier mapping is initially empty. The
|
|
@racket[namespace-require] function, in contrast, directly imports the
|
|
given module into the current namespace. Starting with
|
|
@racket[(namespace-require 'racket/base)] would introduce a binding
|
|
for @racketidfont{require} and make a subsequent @racket[(eval
|
|
'(require my-dsl))] work. The above is better, not only because it is
|
|
more compact, but also because it avoids introducing bindings that are
|
|
not part of the domain-specific languages.
|
|
|
|
@; ----------------------------------------
|
|
|
|
@subsection{Sharing Data and Code Across Namespaces}
|
|
|
|
Modules not attached to a new namespace will be loaded and
|
|
instantiated afresh if they are demanded by evaluation. For example,
|
|
@racketmodname[racket/base] does not include
|
|
@racketmodname[racket/class], and loading @racketmodname[racket/class]
|
|
again will create a distinct class datatype:
|
|
|
|
@interaction[
|
|
(require racket/class)
|
|
(class? object%)
|
|
(class?
|
|
(parameterize ([current-namespace (make-base-empty-namespace)])
|
|
(namespace-require 'racket/class) (code:comment @#,t{loads again})
|
|
(eval 'object%)))
|
|
]
|
|
|
|
For cases when dynamically loaded code needs to share more code and
|
|
data with its context, use the @racket[namespace-attach-module]
|
|
function. The first argument to @racket[namespace-attach-module] is a
|
|
source namespace from which to draw a module instance; in some cases,
|
|
the current namespace is known to include the module that needs to be
|
|
shared:
|
|
|
|
@interaction[
|
|
(require racket/class)
|
|
(class?
|
|
(let ([ns (make-base-empty-namespace)])
|
|
(namespace-attach-module (current-namespace)
|
|
'racket/class
|
|
ns)
|
|
(parameterize ([current-namespace ns])
|
|
(namespace-require 'racket/class) (code:comment @#,t{uses attached})
|
|
(eval 'object%))))
|
|
]
|
|
|
|
Within a module, however, the combination of
|
|
@racket[define-namespace-anchor] and
|
|
@racket[namespace-anchor->empty-namespace] offers a more reliable
|
|
method for obtaining a source namespace:
|
|
|
|
@racketmod[
|
|
racket/base
|
|
|
|
(require racket/class)
|
|
|
|
(define-namespace-anchor a)
|
|
|
|
(define (load-plug-in file)
|
|
(let ([ns (make-base-empty-namespace)])
|
|
(namespace-attach-module (namespace-anchor->empty-namespace a)
|
|
'racket/class
|
|
ns)
|
|
(parameterize ([current-namespace ns])
|
|
(dynamic-require file 'plug-in%))))
|
|
]
|
|
|
|
The anchor bound by @racket[namespace-attach-module] connects the
|
|
run time of a module with the namespace in which a module is loaded
|
|
(which might differ from the current namespace). In the above
|
|
example, since the enclosing module requires
|
|
@racketmodname[racket/class], the namespace produced by
|
|
@racket[namespace-anchor->empty-namespace] certainly contains an
|
|
instance of @racketmodname[racket/class]. Moreover, that instance is
|
|
the same as the one imported into the module, so the class datatype is
|
|
shared.
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
@section[#:tag "load"]{Scripting Evaluation and Using @racket[load]}
|
|
|
|
Historically, Lisp implementations did not offer module
|
|
systems. Instead, large programs were built by essentially scripting
|
|
the @tech{REPL} to evaluate program fragments in a particular order.
|
|
While @tech{REPL} scripting turns out to be a bad way to structure
|
|
programs and libraries, it is still sometimes a useful capability.
|
|
|
|
@margin-note{Describing a program via @racket[load] interacts
|
|
especially badly with macro-defined language extensions
|
|
@cite["Flatt02"].}
|
|
|
|
The @racket[load] function runs a @tech{REPL} script by
|
|
@racket[read]ing S-expressions from a file, one by one, and passing
|
|
them to @racket[eval]. If a file @filepath{place.rkts} contains
|
|
|
|
@racketblock[
|
|
(define city "Salt Lake City")
|
|
(define state "Utah")
|
|
(printf "~a, ~a\n" city state)
|
|
]
|
|
|
|
then it can be loaded in a @tech{REPL}:
|
|
|
|
@interaction[
|
|
(eval:alts (load "place.rkts") (begin (define city "Salt Lake City")
|
|
(printf "~a, Utah\n" city)))
|
|
city
|
|
]
|
|
|
|
Since @racket[load] uses @racket[eval], however, a module like the
|
|
following generally will not work---for the same reasons described in
|
|
@secref["namespaces"]:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(define there "Utopia")
|
|
|
|
(load "here.rkts")
|
|
]
|
|
|
|
The current namespace for evaluating the content of
|
|
@filepath{here.rkts} is likely to be empty; in any case, you cannot get
|
|
@racket[there] from @filepath{here.rkts}. Also, any definitions in
|
|
@filepath{here.rkts} will not become visible for use within the module;
|
|
after all, the @racket[load] happens dynamically, while references to
|
|
identifiers within the module are resolved lexically, and therefore
|
|
statically.
|
|
|
|
Unlike @racket[eval], @racket[load] does not accept a namespace
|
|
argument. To supply a namespace to @racket[load], set the
|
|
@racket[current-namespace] @tech{parameter}. The following example evaluates
|
|
the expressions in @filepath{here.rkts} using the bindings of the
|
|
@racketmodname[racket/base] module:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(parameterize ([current-namespace (make-base-namespace)])
|
|
(load "here.rkts"))
|
|
]
|
|
|
|
You can even use @racket[namespace-anchor->namespace] to make the
|
|
bindings of the enclosing module accessible for dynamic evaluation. In
|
|
the following example, when @filepath{here.rkts} is @racket[load]ed, it
|
|
can refer to @racket[there] as well as the bindings of
|
|
@racketmodname[racket]:
|
|
|
|
@racketmod[
|
|
racket
|
|
|
|
(define there "Utopia")
|
|
|
|
(define-namespace-anchor a)
|
|
(parameterize ([current-namespace (namespace-anchor->namespace a)])
|
|
(load "here.rkts"))
|
|
]
|
|
|
|
Still, if @filepath{here.rkts} defines any identifiers, the definitions
|
|
cannot be directly (i.e., statically) referenced by in the enclosing
|
|
module.
|
|
|
|
The @racketmodname[racket/load] module language is different from
|
|
@racketmodname[racket] or @racketmodname[racket/base]. A module using
|
|
@racketmodname[racket/load] treats all of its content as dynamic,
|
|
passing each form in the module body to @racket[eval] (using a
|
|
namespace that is initialized with @racketmodname[racket]). As a
|
|
result, uses of @racket[eval] and @racket[load] in the module body see
|
|
the same dynamic namespace as immediate body forms. For example, if
|
|
@filepath{here.rkts} contains
|
|
|
|
@racketblock[
|
|
(define here "Morporkia")
|
|
(define (go!) (set! here there))
|
|
]
|
|
|
|
then running
|
|
|
|
@racketmod[
|
|
racket/load
|
|
|
|
(define there "Utopia")
|
|
|
|
(load "here.rkts")
|
|
|
|
(go!)
|
|
(printf "~a\n" here)
|
|
]
|
|
|
|
prints ``Utopia''.
|
|
|
|
Drawbacks of using @racketmodname[racket/load] include reduced
|
|
error checking, tool support, and performance. For example, with the
|
|
program
|
|
|
|
@racketmod[
|
|
racket/load
|
|
|
|
(define good 5)
|
|
(printf "running\n")
|
|
good
|
|
bad
|
|
]
|
|
|
|
DrRacket's @onscreen{Check Syntax} tool cannot tell that the second
|
|
@racket[good] is a reference to the first, and the unbound reference
|
|
to @racket[bad] is reported only at run time instead of rejected
|
|
syntactically.
|