144 lines
5.7 KiB
Racket
144 lines
5.7 KiB
Racket
#lang scribble/doc
|
|
@(require scribble/manual
|
|
scribble/eval
|
|
"guide-utils.ss"
|
|
(for-label (only-in mzscheme fluid-let)))
|
|
|
|
@title[#:tag "parameterize"]{Dynamic Binding: @scheme[parameterize]}
|
|
|
|
@scheme[parameterize] is used to have values that are ``dynamically scoped''.
|
|
You get a parameter with @scheme[make-parameter]. The parameter itself
|
|
behaves as a function: call it with no inputs and you get its value,
|
|
call it with one value and it will set the value. The settings that are
|
|
adjusted by a @scheme[parameterize] form are called @deftech{parameters}.
|
|
For example:
|
|
|
|
@margin-note{The term ``parameter'' is sometimes used to refer to the
|
|
arguments of a function, but ``parameter'' in PLT Scheme
|
|
has the more specific meaning described here.}
|
|
|
|
|
|
@examples[
|
|
(define p (make-parameter "blah"))
|
|
(p)
|
|
(p "meh")
|
|
(p)]
|
|
|
|
Many functions (including many primitive ones) use parameters as a way
|
|
to customize their behavior. For example @scheme[printf] will print text
|
|
using the port that is the value of the @scheme[current-output-port]
|
|
parameter. Now, say that you have some function that prints
|
|
something:
|
|
|
|
@examples[
|
|
(define (foo x) (printf "the value of x is ~s\n"))
|
|
]
|
|
|
|
You usually call this function and see something printed on the screen
|
|
-- but in some cases you want to use it to print something to a file
|
|
or whatever. You could do this:
|
|
|
|
@examples[
|
|
(define (bar)
|
|
(let ([old-stdout (current-output-port)])
|
|
(current-output-port my-own-port)
|
|
(foo some-value)
|
|
(current-output-port old-stdout)))
|
|
]
|
|
|
|
One problem with this is that it is tedious to do -- but that's easily
|
|
solved with a macro. (In fact, PLT still has a construct that does
|
|
that in some languages: @scheme[fluid-let].) But there are more problems
|
|
here: what happens if the call to @scheme[foo] results in a runtime error?
|
|
This might leave the system in a bad state, where all output goes to
|
|
your port (and you won't even see a problem, since it won't print
|
|
anything). A solution for that (which @scheme[fluid-let] uses too) is to
|
|
protect the saving/restoring of the parameter with @scheme[dynamic-wind],
|
|
which makes sure that if there's an error (and more, if you know about
|
|
continuations) then the value is still restored.
|
|
|
|
So the question is what's the point of having parameters instead of
|
|
just using globals and @scheme[fluid-let]? There are two more problems that
|
|
you cannot solve with just globals. One is what happens when you have
|
|
multiple threads -- in this case, setting the value temporarily will
|
|
affect other threads, which may still want to print to the standard
|
|
output. Parameters solve this by having a specific value per-thread.
|
|
What happens is that each thread ``inherits'' the value from the thread
|
|
that created it, and changes in one thread are visible only in that
|
|
thread.
|
|
|
|
The other problem is more subtle. Say that you have a parameter with
|
|
a numeric value, and you want to do the following:
|
|
|
|
@examples[
|
|
(define (foo)
|
|
(parameterize ([p 'any-expression-goes-here])
|
|
(foo)))
|
|
]
|
|
|
|
In Scheme, ``tail calls'' are important -- they are the basic tool for
|
|
creating loops and much more. @scheme[parameterize] does some magic that
|
|
allows it to change the parameter value temporarily but still preserve
|
|
these tail calls. For example, in the above case, you @bold{will} get an
|
|
infinite loop, rather than get a stack overflow error -- what happens
|
|
is that each of these @scheme[parameterize] expressions can somehow detect
|
|
when there's an earlier @scheme[parameterize] that no longer needs to do its
|
|
cleanup.
|
|
|
|
Finally, @scheme[parameterize] actually uses two important parts of PLT to do
|
|
its job: it uses thread cells to implement per-thread values, and it
|
|
uses continuation marks to be able to preserve tail-calls. Each of
|
|
these features is useful in itself.
|
|
|
|
@specform[(parameterize ([parameter-expr value-expr] ...)
|
|
body ...+)]
|
|
|
|
The result of a @scheme[parameterize] form is the result of the last
|
|
@scheme[_body] expression. While the @scheme[_body] expressions are
|
|
evaluated, the parameter produced by each @scheme[_parameter-expr] is
|
|
set to the result of the corresponding @scheme[_value-expr].
|
|
|
|
Many parameters are built in. For example, the
|
|
@scheme[error-print-width] parameter controls how many characters of a
|
|
value are printed in an error message (in case the printed form of the
|
|
value is very large):
|
|
|
|
@interaction[
|
|
(parameterize ([error-print-width 10])
|
|
(car (expt 10 1024)))
|
|
(parameterize ([error-print-width 5])
|
|
(car (expt 10 1024)))
|
|
]
|
|
|
|
The @scheme[error-print-width] parameter acts like a kind of default
|
|
argument to the function that formats error messages. This
|
|
parameter-based argument can be configured far from the actual call to
|
|
the error-formatting function, which in this case is called deep
|
|
within the implementation of @scheme[car].
|
|
|
|
The @scheme[parameterize] form adjusts the value of a parameter only
|
|
while evaluating its body expressions. After the body produces a
|
|
value, the parameter reverts to its previous value. If control escapes
|
|
from the body due to an exception, as in the above example, then the
|
|
parameter value is restored in that case, too. Finally, parameter
|
|
values are thread-specific, so that multiple threads do not interfere
|
|
with each others' settings.
|
|
|
|
Use @scheme[make-parameter] to create a new parameter that works with
|
|
@scheme[parameterize]. The argument to @scheme[make-parameter] is the
|
|
value of the parameter when it is not otherwise set by
|
|
@scheme[parameterize]. To access the current value of the parameter,
|
|
call it like a function.
|
|
|
|
@interaction[
|
|
(define favorite-flavor (make-parameter 'chocolate))
|
|
(favorite-flavor)
|
|
(define (scoop)
|
|
`(scoop of ,(favorite-flavor)))
|
|
(define (ice-cream n)
|
|
(list (scoop) (scoop) (scoop)))
|
|
(parameterize ([favorite-flavor 'strawberry])
|
|
(ice-cream 3))
|
|
(ice-cream 3)
|
|
]
|