In this example, the define/freevar macro introduces function
definitions with free variables in their body.
The free variables are resolved non-hygienically to any
bindings of an equal symbol name at each use site.
The idea is transforming the original definition into a lambda function
that accepts the free variables and generating a new macro
which inserts the unhygienic references for the free variables at each
use site.
Here is an example illustrating the idea. The function raise-who-error
raises a syntax error and uses whichever binding named who
available as the name of the error message.
Conceptually, thedefine/freevar form expands into a new definition
having the original code and a new macro for generating references of
the free variables:
The new macro raise-who-error creates a reference, who/use-site,
to be captured non-hygienically using the context from the use site.
The expansion then proceeds with the use-site reference and calls
the original code.
Additionally, the use-site references have the source location of the
proc-src and the syntax property 'original-for-check-syntax
so Check Syntax and DrRacket can draw the binding arrows.
Caveat: mutation on the free variables will
not reflect on the original binding. Such a restriction can be overcome
using set!-transformers. The macro define/freevar can also disallow
mutation using make-variable-like-transformer.
Implementation
While the idea is straightforward, a direct translation generates a large
amount of code duplication. In the output of define/freevar, the only
varying parts are the names of the free variables and the identifier
of the actual implementation. The implementation of define/freevar
thus follows a common pattern in Racket to share the transformer code.
The define/freevar form expands to a new definition storing the
original code and a macro for binding the free identifiers.
The implementation introduces an applicative struct, open-term,
that holds the list of free variables and the identifier of
the actual code.
Being applicative, open-term also has the implementation of the
use-site macro and serves as the transformer in the expansion of
for define/freevar.
When the macro expander calls an instance of open-term, it extracts
names of the free variables and redirects the reference to the
actual code.
The idea behind custom pattern expanders and syntax class aliases are related: using structs to store varying information while attaching struct type properties to assign behavior.
In this example, we define a function for computing the Fibonacci
sequence where the base values are left open and resolved at each
use site.
To illustrate the syntax, fib uses the option #:immediate that
immediately retrieve the value of init0 and init1 instead of
wrapping the identifier reference fib at X in a function.
(define/freevar (fib n)
#:freevars (init0 init1)
#:immediate
(for/fold ([a init0]
[b init1]
[fib-list '()]
#:result (reverse fib-list))
([i (in-range n)])
(values b (+ a b) (cons a fib-list))))
(define init0 2)
;; X
(let ([init1 13])
fib) ;; <- The #:immediate flag makes a difference
;; init0 shadows the global definition
;;=> '(0 1 1 2 3 5 8 ...)
(let ([init0 0]
[init1 1])
(fib 10))
;; The free variable init1 is renamed to b
(with-freevar fib ([init1 b])
(define b 4)
(fib 10))
;; Another renaming example. Free variables do not have bindings.
(let ([b 5])
(with-freevar fib ([init1 b])
(fib 10)))
;; Define a new open term, fib-same, with free variables renamed from fib.
(define/with-freevar fib-same fib
[init0 S]
[init1 S])
(let ([S 3])
(fib-same 10))
For the interested readers, the motivating example of define/freevar
is the following utility function for Redex:
I license the code in this issue under the same MIT License that the Racket language uses and the texts under the Creative Commons Attribution 4.0 International License
Macro
Overview
In this example, the
define/freevar
macro introduces function definitions with free variables in their body. The free variables are resolved non-hygienically to any bindings of an equal symbol name at each use site.In conjunction with
define/freevars
, thewith-freevar
macro locally renames the free variables for definitions introduced usingdefine/freevars
.There is also the
define
counterpart ofwith-freevar
:Idea
The idea is transforming the original definition into a lambda function that accepts the free variables and generating a new macro which inserts the unhygienic references for the free variables at each use site.
Here is an example illustrating the idea. The function
raise-who-error
raises a syntax error and uses whichever binding namedwho
available as the name of the error message.Conceptually, the
define/freevar
form expands into a new definition having the original code and a new macro for generating references of the free variables:The new macro
raise-who-error
creates a reference,who/use-site
, to be captured non-hygienically using the context from the use site. The expansion then proceeds with the use-site reference and calls the original code.Additionally, the use-site references have the source location of the
proc-src
and the syntax property'original-for-check-syntax
so Check Syntax and DrRacket can draw the binding arrows.Caveat: mutation on the free variables will not reflect on the original binding. Such a restriction can be overcome using
set!
-transformers. The macrodefine/freevar
can also disallow mutation usingmake-variable-like-transformer
.Implementation
While the idea is straightforward, a direct translation generates a large amount of code duplication. In the output of
define/freevar
, the only varying parts are the names of the free variables and the identifier of the actual implementation. The implementation ofdefine/freevar
thus follows a common pattern in Racket to share the transformer code.define/freevar
form expands to a new definition storing the original code and a macro for binding the free identifiers.The implementation introduces an applicative struct,
open-term
, that holds the list of free variables and the identifier of the actual code.Being applicative,
open-term
also has the implementation of the use-site macro and serves as the transformer in the expansion of fordefine/freevar
.open-term
, it extracts names of the free variables and redirects the reference to the actual code.The idea behind custom pattern expanders and syntax class aliases are related: using structs to store varying information while attaching struct type properties to assign behavior.
The
open-term
itself can be used as a transformer, with the list of free variables and the target identifier differs in different instances:Example
In this example, we define a function for computing the Fibonacci sequence where the base values are left open and resolved at each use site.
To illustrate the syntax,
fib
uses the option#:immediate
that immediately retrieve the value ofinit0
andinit1
instead of wrapping the identifier referencefib
at X in a function.For the interested readers, the motivating example of
define/freevar
is the following utility function for Redex:Licence
I license the code in this issue under the same MIT License that the Racket language uses and the texts under the Creative Commons Attribution 4.0 International License