racket/collects/scribblings/guide/parameterize.scrbl
Jon Rafkind 4f4a8bc7c3 minor adjustments
svn: r17296
2009-12-14 20:18:44 +00:00

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