racket/collects/scribblings/guide/parameterize.scrbl
2010-04-27 14:09:51 -06:00

164 lines
5.0 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss"
(for-label (only-in mzscheme fluid-let)))
@(define param-eval (make-base-eval))
@title[#:tag "parameterize"]{Dynamic Binding: @racket[parameterize]}
@refalso["parameters"]{@racket[parameterize]}
The @racket[parameterize] form associates a new value with a
@deftech{parameter} during the evaluation of @racket[_body]
expressions:
@specform[(parameterize ([parameter-expr value-expr] ...)
body ...+)]
@margin-note{The term ``parameter'' is sometimes used to refer to the
arguments of a function, but ``parameter'' in Racket
has the more specific meaning described here.}
For example, the @racket[error-print-width] parameter controls how
many characters of a value are printed in an error message:
@interaction[
(parameterize ([error-print-width 5])
(car (expt 10 1024)))
(parameterize ([error-print-width 10])
(car (expt 10 1024)))
]
More generally, parameters implement a kind of dynamic binding. The
@racket[make-parameter] function takes any value and returns a new
parameter that is initialized to the given value. Applying the
parameter as a function returns its current value:
@interaction[
#:eval param-eval
(define location (make-parameter "here"))
(location)
]
In a @scheme[parameterize] form, each @racket[_parameter-expr] must
produce a parameter. During the evaluation of the @scheme[body]s, each
specified parameter is given the result of the corresponding
@scheme[_value-expr]. When control leaves the @racket[parameterize]
form---either through a normal return, an exception, or some other
escape---the parameter reverts to its earlier value:
@interaction[
#:eval param-eval
(parameterize ([location "there"])
(location))
(location)
(parameterize ([location "in a house"])
(list (location)
(parameterize ([location "with a mouse"])
(location))
(location)))
(parameterize ([location "in a box"])
(car (location)))
(location)
]
The @scheme[parameterize] form is not a binding form like
@scheme[let]; each use of @racket[location] above refers directly to
the original definition. A @scheme[parameterize] form adjusts the
value of a parameter during the whole time that the
@scheme[parameterize] body is evaluated, even for uses of the
parameter that are textually outside of the @racket[parameterize]
body:
@interaction[
#:eval param-eval
(define (would-you-could-you?)
(and (not (equal? (location) "here"))
(not (equal? (location) "there"))))
(would-you-could-you?)
(parameterize ([location "on a bus"])
(would-you-could-you?))
]
If a use of a parameter is textually inside the body of a
@racket[parameterize] but not evaluated before the
@racket[parameterize] form produces a value, then the use does not see
the value installed by the @racket[parameterize] form:
@interaction[
#:eval param-eval
(let ([get (parameterize ([location "with a fox"])
(lambda () (location)))])
(get))
]
The current binding of a parameter can be adjusted imperatively by
calling the parameter as a function with a value. If a
@racket[parameterize] has adjusted the value of the parameter, then
directly applying the parameter procedure affects only the value
associated with the active @racket[parameterize]:
@interaction[
#:eval param-eval
(define (try-again! where)
(location where))
(location)
(parameterize ([location "on a train"])
(list (location)
(begin (try-again! "in a boat")
(location))))
(location)
]
Using @racket[parameterize] is generally preferable to updating a
parameter value imperatively---for much the same reasons that binding
a fresh variable with @scheme[let] is preferable to using
@scheme[set!] (see @secref["set!"]).
It may seem that variables and @racket[set!] can solve many of the
same problems that parameters solve. For example, @racket[lokation]
could be defined as a string, and @racket[set!] could be used
to adjust its value:
@interaction[
#:eval param-eval
(define lokation "here")
(define (would-ya-could-ya?)
(and (not (equal? lokation "here"))
(not (equal? lokation "there"))))
(set! location "on a bus")
(would-ya-could-ya?)
]
Parameters, however, offer several crucial advantages over
@scheme[set!]:
@itemlist[
@item{The @racket[parameterize] form helps automatically reset the
value of a parameter when control escapes due to an exception.
Adding exception handlers and other forms to rewind a
@scheme[set!] is relatively tedious.}
@item{Parameters work nicely with tail calls (see
@secref["tail-recursion"]). The last @racket[_body] in a
@racket[parameterize] form is in @tech{tail position} with
respect to the @racket[parameterize] form.}
@item{Parameters work properly with threads (see
@refsecref["threads"]). The @scheme[parameterize] form adjusts
the value of a parameter only for evaluation in the current
thread, which avoids race conditions with other threads.}
]
@; ----------------------------------------
@close-eval[param-eval]