Description
Macro
Some macros need to generate a sequence of fresh identifiers corresponding to a list of input forms. The standard solution is invoking generate-temporaries
with a syntax list and bind the result to a new pattern variable. However, the new pattern variable is disconnected from the input forms and such an approach quickly becomes unmanageable when the input forms come nested in more than one ellipses.
By delegating the work to syntax classes, the syntax attributes tightly couples the generate identifiers with the input forms (and they even have DrRacket binding arrows). Moreover, repetition handling is entirely done by syntax/parse, thereby simplifying the code.
#lang racket/base
(require (for-syntax racket/base
racket/syntax
syntax/parse))
(provide (for-syntax fresh-variable))
(begin-for-syntax
;; I'm not sure if `context` is ever going to be useful but I added it anyway.
(define-syntax-class (fresh-variable [context #f])
#:attributes (fresh-var)
(pattern name
#:with temp-var (generate-temporary #'name)
#:with fresh-var (if context
(format-id context "~a" #'temp-var)
#'temp-var))))
Example
In this example, we create a define/immutable-parameter
form for defining function while disabling mutation of the parameters through make-variable-like-transformer
.
The macro define/immutable-parameter
parses the arguments arg
with fresh-variable
to generate temporary identifiers on-the-fly. Each of the fresh identifier is directly paired with the original identifier through syntax attributes.
(require (for-syntax syntax/transformer))
(define-syntax (define/immutable-parameter stx)
(syntax-parse stx
[(_ (name:id (~or* (~and _:id arg:fresh-variable)
[(~and _:id arg:fresh-variable) default-value:expr])
...)
body:expr ...+)
#'(define (name (~? [arg.fresh-var default-value]
arg.fresh-var) ...)
;; disable set! on arg
(define-syntax arg
(make-variable-like-transformer #'arg.fresh-var #f))
...
body ...)]))
(define/immutable-parameter (fib n [verbose? #f])
; (set! n 12345) ;=> syntax error
(when verbose?
(printf "(fib ~a)\n" n))
(cond
[(<= n 1) n]
[else
(+ (fib (- n 1) verbose?)
(fib (- n 2) verbose?))]))
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