Racket parameters let you manage stateful global variables in a way that feels more functional and is also thread- and continuation-safe. A convenient
parameterize form lets you change and restore them. I’ll discuss this and show how I map parameters to a configuration file.
Coming from C/C++, at first I wasn’t sure what to make of Racket parameters. Telling me, “They’re similar to Lisp’s
defvar” wouldn’t have helped. Neither would, “Parameters correspond to preserved thread fluids in Scsh”, as says the reference.
First, there’s some potential jargon confusion. Whereas a C person would talk about “calling a function with arguments” or “passing arguments to a function”, a Racketeer might sometimes talk about “applying parameters to a function”. In that usage, “parameter” means “argument”.
But the “parameters” I’m talking about aren’t function arguments. In fact Racket parameters can be an alternative to function arguments, as we’ll see below.
One way to look at parameters is that, although we prefer to be purely functional, sometimes it’s more practical to have some stateful global variables, and parameters are a saner way to do that.
The function signature
A parameter is a function that takes one, optional argument. If you call the function with no arguments, it returns the existing value. If you give the function a value, that becomes the new value of the parameter1.
1 2 3 4
By convention, parameters are named with the prefix
current-. That’s somewhat verbose, and it’s not an absolute rule. But if you’re writing code for another Racketeer, using
current- will afford “I’m a parameter”.
Racket predefines some parameters you may already know, such as
If parameters were only a stateful function, you could write them using the usual “let over lambda” pattern for a closure:
However parameters do more than this. They use thread cells, meaning that each thread gets its own copy of the parameter. This is one of the ways in which using parameters is a saner way to deal with stateful global variables—threads won’t stomp on each others’ values.
Often you’ll use a parameter to avoid passing excessive “configuration” arguments to a function. For example, you could have this:
1 2 3 4
(define (fribble interesting-arg option-1 option-2 option-3) ;; consult option-X args for how to behave doing ;; something to `interesting-arg` )
But let’s say only
interesting-arg tends to vary for each call. The
option-x args tend to be the same. Requiring every call site to pass them would be tedious and even error-prone, especially should you ever redesign to change or add options.
Using parameters you can instead do this:
1 2 3 4 5 6 7
This way, the options can be specified only as/when needed.
Another advantage of parameters is a handy form to:
Set them2 to new values.
Automatically restore the original values.
The form is called
1 2 3 4 5
(parameterize ([fribble-option-1 42] [fribble-option-2 43] [fribble-option-3 44]) (fribble x)) ;; Then out here, the original values of fribble-option-x are restored
For example if you want to temporarily redirect and capture output to a string:
1 2 3 4 5 6
displayln does accept an optional argument for the
output-port to which to display. You could keep passing
o to each call. But using
parameterize avoids that repetition. That would matter more when there were many more call sites (unlike the simple example above) and/or when there were many more configuration options (unlike
displayln). Anyway, some functions might not have any such optional argument and can only be controlled via a parameter.
Jay McCarthy has an excellent series of blog posts about continuation marks: Part I, Part II, Part III.
In Part II he compares the performance of
parameterize. The take-away:
dynamic-wind is faster,
parameterize uses less space.
If you’ve profiled and know you have a hot, tight loop, inside it you might want to use
dynamic-wind rather than
parameterize. Otherwise I’d suggest avoiding premature optimization and not worrying about it.
In writing Frog I had a number of parameters that I wanted to be loaded from a
.frogrc configuration file.
Also, although I followed the
current- naming convention for the parameters, that didn’t seem so friendly for the dot file. So I wanted
current-foo to map to a dot file variable named just
Although I suspect it could be improved, what I came up with is the following
1 2 3 4 5 6 7 8 9 10 11 12 13
(require (for-syntax racket/syntax)) (define-syntax (parameterize-from-config stx) (syntax-case stx () [(_ ([name default] ...) body ...) (with-syntax ([(id ...) (map (lambda (x) (format-id stx "current-~a" x)) (syntax->list #'(name ...)))] [(key ...) (map (lambda (x) (symbol->string (syntax-e x))) (syntax->list #'(name ...)))]) #'(parameterize ([id (get-config key default)] ...) body ...))]))
Some helper functions are omitted; full source here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
(module+ main (parameterize ([top (current-directory)]) (parameterize-from-config ([scheme/host raise-config-required-error] [title "Untitled Site"] [author "The Unknown Author"] [index-full? #f] [feed-full? #f] [google-analytics-account #f] [google-analytics-domain #f] [disqus-shortname #f] [pygments-pathname #f] [decorate-feed-uris? #t] [feed-image-bugs? #f] [older/newer-buttons "both"]) (command-line ....
Parameters are a way to handle stateful global variables in a way that appears more functional, and is thread- and continuation-safe. This makes it possible to move “configuration” options out of function arguments. Finally, the
parameterize form makes it convenient to change parameters for awhile, then assure that the original values are restored.