You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 functiondefinitions 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
macrolocally renames the free variables for definitions introduced
using
define/freevars
.There is also the
define
counterpart ofwith-freevar
:(define/with-freevar new-function-id old-function-id [freevar-id new-freevar-id] ...)
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 named
who
available as the name of the error message.
Conceptually, the
define/freevar
form expands into a new definitionhaving 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 disallowmutation 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 onlyvarying 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 theoriginal 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 theuse-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 extractsnames 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 freevariables 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
thatimmediately retrieve the value of
init0
andinit1
instead ofwrapping the identifier reference
fib
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
The text was updated successfully, but these errors were encountered: