Template Macros
(require template) | package: template |
1 Overview
Template macros are similar to pattern-based macros, but with four important differences:
Template variables are resolved within every internable datum and can be combined to synthesize non-identifier literals.
> (with-template ([$x 3] [$y 2]) (add1 $x$y0)) 321
Template variables are always in scope, regardless of position, quoting depth, or escape level.
> (begin-template '((for/template ([$a 3]) #'"$a"))) '(#'"0" #'"1" #'"2")
Most templates become splicing forms when used inside other templates.
> (begin-template (list (for/template ([$k 10]) (add1 $k)))) '(1 2 3 4 5 6 7 8 9 10)
Templates can escape to the expanding environment, even when they appear outside of a syntax transformer.
> (begin-template '(#,@(list #'hello #'world))) '(hello world)
The template API splits ordinary macro expansion into two distinct stages: template expansion time and macro expansion time. Always, template variables are resolved and sub-template forms are expanded before ordinary macros are expanded.
1.1 The ‘$’ Convention
Throughout this manual, the names of template variables start with a ‘$’. Although the template API imposes no such restriction on variable names, beware:
Template macro variable resolution is finer-grained than ordinary variable resolution. Poorly chosen variable names can lead to bizarre syntax errors.
> (with-template ([e X]) (define e 123)) dXfinX: undefined;
cannot reference an identifier before its definition
in module: 'program
> (with-template ([e X]) 'e) quotX: undefined;
cannot reference an identifier before its definition
in module: 'program
2 Primitives
syntax
(with-template ([var-id val-expr] ...) tpl ...)
> (with-template ([$x a] [$y b]) (define $xs '($x $x $x)) (displayln `($xs = ,@$xs))) (as = a a a)
When a with-template form appears at the top level, at module level, or in an internal-definition position (before any expression in the internal-definition sequence), it is equivalent to splicing the expanded tpls into the enclosing context.
> (with-template ([$x a] [$y b]) (define ($x-$y? obj) (equal? obj '($x $y)))) > (a-b? '(a b)) #t
> (a-b? '(c d)) #f
> (list (with-template ([$x a]) '$x) (with-template ([$x b]) '$x)) '(a b)
syntax
(semiwith-template ([var-id val-expr] ...) tpl ...)
> (semiwith-template ([$where here]) #'$where) #<syntax:eval:162:28 here>
> (with-template ([$where not-here]) #'$where) #<syntax:eval:166:37 not-here>
syntax
(quote-template ([var-id val-expr] ...) tpl ...)
> (quote-template ([$where not-here]) #'(untemplate '$where)) #<syntax:eval:176:38 (untemplate (quote not-here))>
> (semiwith-template ([$where there]) #'(untemplate '$where)) #<syntax:eval:180:50 there>
syntax
(semiquote-template ([var-id val-expr] ...) tpl ...)
> (semiquote-template ([$where not-here]) #'(untemplate '$where)) #<syntax:eval:193:42 (untemplate (quote not-here))>
> (quote-template ([$where not-here]) #'(untemplate '$where)) #<syntax:eval:197:38 (untemplate (quote not-here))>
3 Constructors
syntax
(template (var-id ...) tpl ...)
syntax
(semitemplate (var-id ...) tpl ...)
syntax
(quoted-template (var-id ...) tpl ...)
syntax
(semiquoted-template (var-id ...) tpl ...)
> (define-syntax iterate-with (template ($for) ($for/list ([x (in-range 3)] [y (in-range 3)]) (+ y (* x 3))))) > (iterate-with for) '(0 4 8)
> (iterate-with for*) '(0 1 2 3 4 5 6 7 8)
syntax
(templates [(var-id ...) tpl ...] ...)
syntax
(semitemplates [(var-id ...) tpl ...] ...)
syntax
(quoted-templates [(var-id ...) tpl ...] ...)
syntax
(semiquoted-templates [(var-id ...) tpl ...] ...)
> (define-syntax f (templates [() 0] [($x) '$x] [($x $y) '$x-$y])) > (list (f) (f one) (f one two)) '(0 one one-two)
> (f one two three) eval:251:12: f: bad syntax
in: (f one two three)
syntax
(untemplate expr)
syntax
(untemplate-splicing expr)
> (let-syntax ([x #'(+ 2 3)]) (begin-template (+ 1 (untemplate (syntax-local-value #'x))))) 6
> (begin-template '(1 (untemplate-splicing '(2 3)))) '(1 2 3)
If expr does not evaluate to a syntax object, the result is wrapped in the lexical information, source-location information, and syntax properties of expr.
> (begin-template #'(untemplate 'here)) #<syntax:eval:279:30 here>
> (begin-template #'(untemplate (datum->syntax #f 'nowhere))) #<syntax nowhere>
4 Combiners
syntax
(begin-template tpl ...)
> (begin-template 1 2 3) 3
syntax
(begin0-template ([var-id val-expr] ...) tpl ...)
> (begin0-template 1 2 3) 1
syntax
(if-template test-expr then-tpl else-tpl)
> (begin-template `(if-template (positive? -5) ,(error "doesn't get here") >>)) '>>
> (begin-template `(if-template (positive? 5) << ,(error "doesn't get here"))) '<<
> (let-syntax ([x 'we-have-no-bananas]) (with-template ([$+ yes] [$- no]) (if-template (syntax-local-value #'x) "$+" "$-"))) "yes"
> (let-syntax ([x #f]) (with-template ([$+ yes] [$- no]) (if-template (syntax-local-value #'x) "$+" "$-"))) "no"
syntax
(cond-template [test-expr tpl ...] ... maybe-else-clause)
maybe-else-clause =
| [else tpl ...]
If no clauses are present, (void) takes the place of the cond-template form.
If the first clause does not start with else and its test-expr, which is an expression at phase level 1 relative to the surrounding context, produces #f, then the result is the same as a cond-template form with the remaining clauses. Otherwise, the tpls take the place of the cond-template form.
> (cond-template) > (cond-template [else 5]) 5
> (let-syntax ([x #f] [y #t]) (begin-template `(cond-template [(positive? -5) ,(error "doesn't get here")] [(syntax-local-value #'x) ,(error "doesn't get here, either")] [(syntax-local-value #'y) here]))) 'here
syntax
(when-template test-expr tpl ...)
> (displayln (when-template #f 'hi)) #<void>
> (begin-template (list (when-template #t 'hey 'there))) '(hey there)
syntax
(unless-template test-expr tpl ...)
> (displayln (unless-template #f 'hi)) hi
> (begin-template (list (unless-template #t 'hey 'there))) '()
syntax
(for/template ([var-id seq-expr] ...) tpl ...)
> (for/template ([$x (in-syntax #'(A B C))] [$n (in-naturals)]) (define $x (add1 $n))) > (list A B C) '(1 2 3)
When for/template is used in an expression context inside a template, the results are spliced into the enclosing context.
> (begin-template (list (for/template ([$n (in-range 10)]) $n))) '(0 1 2 3 4 5 6 7 8 9)
When used outside any other template, the results are wrapped with begin.
> (list (for/template ([$n 10]) (add1 $n))) '(10)
syntax
(for*/template ([var-id seq-expr] ...) tpl ...)
> (begin-template (list (for*/template ([$m (in-range 3)] [$n (in-range 3)]) (+ $n (* $m 3))))) '(0 1 2 3 4 5 6 7 8)
5 Binding Forms
syntax
(define-template (id var-id ...) tpl ...)
> (define-template (iterate-with $for) ($for/list ([x (in-range 3)] [y (in-range 3)]) (+ y (* 3 x)))) > (iterate-with for) '(0 4 8)
> (iterate-with for*) '(0 1 2 3 4 5 6 7 8)
syntax
(define-templates [(id var-id ...) tpl ...] ...)
> (define-templates [(show $obj) (displayln "$obj")] [(many $objs) (for/template ([$obj '$objs]) (show $obj))]) > (many (one two three))
one
two
three
syntax
(let-template ([(id var-id ...) tpl ...] ...) body-tpl ...)
> (let-template ([(fwd $x $y) $x$y] [(rev $x $y) $y$x]) '((fwd a b) (rev a b))) '(ab ba)
syntax
(letrec-template ([(id var-id ...) tpl ...] ...) body-tpl ...)
> (letrec-template ([(E? $n) (if-template (zero? $n) #t (O? #,(sub1 $n)))] [(O? $n) #,(not (E? $n))]) '((E? 10) (E? 11))) '(#t #f)
syntax
(splicing-let-template ([(id var-id ...) tpl ...] ...) body-tpl ...)
syntax
(splicing-letrec-template ([(id var-id ...) tpl ...] ...) body-tpl ...)
> (splicing-let-template ([(one) 1]) (define o (one))) > o 1
> (one) one: undefined;
cannot reference an identifier before its definition
in module: 'program
> (splicing-letrec-template ([(E? $n) (if-template (zero? $n) #t (O? #,(sub1 $n)))] [(O? $n) #,(not (E? $n))]) (define is-11-even? (E? 11)) (define is-10-even? (E? 10))) > (list is-11-even? is-10-even?) '(#f #t)
6 Module Templates
In template/tests/lang-template.rkt:
#lang template ($x $n) (require (for-syntax racket/base)) (define $xs '((for/template ([$_ (in-range 1 (add1 $n))]) $x))) (for/template ([$k (in-range 1 (add1 $n))]) (define $x$k $k))
syntax
(load-template mod-path id)
> (load-template template/scribblings/lang-template tpl) > (tpl a 4) > as '(a a a a)
> a4 4
syntax
(template-module-begin (var-id ...) tpl ...)
> (module my-template-mod template/lang ($x) (define $xs '($x $x $x $x))) > (require 'my-template-mod) > (the-template b) > bs '(b b b b)
7 Developer Tools
syntax
(debug-template tpl)
> (debug-template (for/template ([$n 10]) $n)) (begin 0 1 2 3 4 5 6 7 8 9)
9
> (debug-template (begin-template (list (for/template ([$n 10]) $n)))) (list 0 1 2 3 4 5 6 7 8 9)
'(0 1 2 3 4 5 6 7 8 9)
syntax
(debug-template/scopes tpl)
> (debug-template/scopes (for/template ([$x (in-syntax #'(A B C D E))] [$n (in-range 1 6)]) #'$x$n))
(begin⁰˙˙²
(syntax⁰˙˙² A1²˙˙³)
(syntax⁰˙˙² B2²˙˙³)
(syntax⁰˙˙² C3²˙˙³)
(syntax⁰˙˙² D4²˙˙³)
(syntax⁰˙˙² E5²˙˙³))
0 module main -81293
1 module -81289
2 module top-level 161633/2
3 module 0
#<syntax:eval:599:6 E5>
syntax