A parameter is a special kind of function that approximates the behavior of a global variable. An ordinary Racket value can be mutated with set!:
But there are some restrictions. For instance, we can’t mutate variables across a module boundary:
Parameters remove these restrictions. A parameter is created with make-parameter and an initial value. After that, calling the parameter without an argument retrieves its current value. Passing the parameter an argument changes its value:
1 2 3 4 5 6 7 8 | (module mod br (provide x) (define x (make-parameter 42))) (require 'mod) (x) ; 42 (x 43) (x) ; 43 |
Parameters can be mutated within the scope of an expression by wrapping it with parameterize, which consists of a let-like list of binding pairs, with each parameter name on the left, and the value on the right:
1 2 3 4 5 | (define current-list (make-parameter '(0 1 2 3 4))) (displayln (car (current-list))) ; 0 (parameterize ([current-list '(a b c d e)]) (displayln (car (current-list)))) ; a (displayln (car (current-list))) ; 0 |
Here, parameterize changes the value of current-list only for the nested subexpressions. After the parameterize exits, current-list automatically reverts to its previous value.
As usual in Racket, parameterize recognizes relevant parameters not just by their name, but by their binding. In the example below, the param and fun modules both define a parameter called x. But the parameterize refers to the x parameter bound in param, so the other is unchanged:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | (module param br (provide x) (define x (make-parameter 42))) (module fun br (provide f) (define x (make-parameter 42)) (define (f) (x))) (require 'param 'fun) (parameterize ([x 101]) (println (x)) ; 101 (println (f))) ; 42 |
If we want this parameterization to work as intended, then the fun submodule has to import the parameter binding from param as well:
Dynamic binding: parameterize in the Racket Guide
Parameters in the Racket Reference
Syntax parameters are analogous to ordinary parameters. Whereas ordinary parameters dynamically rebind values to identifiers at run time, syntax parameters dynamically rebind macros to identifiers at compile time.
A syntax parameter is introduced with define-syntax-parameter and then rebound with syntax-parameterize. The value of a syntax parameter is, like all macros, a function that takes one syntax object as input and returns another syntax object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Note that x evaluates to 101 (because it’s rebound by syntax-parameterize). But why isn’t f affected? Because syntax-parameterize operates at compile time. When the fun module is imported, the code (define f x) is rewritten as (define f 42) by syntax-parameter substitution. Thus the later syntax-parameterize can’t alter it.
Syntax parameters are useful for introducing identifiers within the body of a form. These are sometimes known as anaphoric identifiers. For instance, below is a macro called begin/return that returns the value passed to the identifier called return:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (require racket/stxparam) (define-syntax-parameter return (λ (stx) #''default)) (define-macro (begin/return . BODY) (with-syntax ([CC (generate-temporary)]) #'(let/cc CC (displayln (format "CC = ~a" 'CC)) (syntax-parameterize ([return (make-rename-transformer #'CC)]) . BODY)))) (return 2) ; 'default (begin/return ; CC = [randomish id] 1 (return 2) 3) ; 2 |
Under the hood, return is a syntax parameter. Outside the begin/return, this syntax parameter expands to its default value. But within begin/return, syntax-parameterize uses make-rename-transformer to make return an alias for CC (which in turn has a randomish identifier made by generate-temporary). Thus, even though the name of CC will change on recompile, we can still capture this name and bind it to return throughout the syntax represented by BODY.
Syntax parameters overlap with unhygienic identifiers (see hygiene), but tend to be better behaved. For instance, if we implement return as an unhygienic identifier, the return only has meaning within begin/return, and produces an unbound-identifier error outside:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (define-macro (begin/return . BODY) (with-syntax ([CC (generate-temporary)] [RETURN (datum->syntax caller-stx 'return)]) #'(let/cc CC (displayln (format "CC = ~a" 'CC)) (let-syntax ([RETURN (make-rename-transformer #'CC)]) . BODY)))) (return 2) ; error: unbound identifier (begin/return ; CC = g1 1 (return 2) 3) ; 2 |
In essence, define-syntax-parameter creates a “dummy” binding for return that allows the source to compile, and then syntax-parameterize rebinds it to something useful later on.
One disadvantage of syntax parameters is their names and quantity must be known in advance. So while they work well for keywordish identifiers like return, they wouldn’t work for variables in BASIC, because we can’t know in advance how many variables will exist, or what their names will be.
Syntax parameters in the Racket Reference