Template Macros
1 Overview
1.1 Fine-Grained Variable Resolution
1.2 Expansion-Driven Template Generation
1.3 The ‘$’-Prefix Convention
2 Construction
template
templates
untemplate
untemplate-splicing
3 Binding Forms
define-template
let-template
letrec-template
splicing-let-template
splicing-letrec-template
4 Sequence, Selection, Iteration
begin-template
begin0-template
if-template
cond-template
when-template
unless-template
for/  template
for*/  template
5 Identifier Sequences
define-template-ids
in-template-ids
6 Modules
load-template-module
#%module-begin
7.5

Template Macros

Eric Griffis <dedbox@gmail.com>

 (require template) package: template

1 Overview

Template macros are smilar to pattern-based macros, but with two major differences:

  1. Variable resolution occurs within every identifier and at the character level.

  2. Code may be generated iteratively or recursively during expansion of the template body.

1.1 Fine-Grained Variable Resolution

Template macros can generate many identifiers from a common base.

; A bjiective composition is a pair of functions that invert
; each other when composed.
> (define-template (define-bijective-composition $T1 $E1 $T2 $E2)
    (define/contract $T1->$T2 (-> $T1? $T2?) $E2)
    (define/contract $T2->$T1 (-> $T2? $T1?) $E1))
> (define-bijective-composition
    real (compose read open-input-string)
    string (curry format "~a"))
> (string->real (real->string 123))

123

> (real->string (string->real "987"))

"987"

> (string->real -1)

string->real: contract violation

  expected: string?

  given: -1

  in: the 1st argument of

      (-> string? real?)

  contract from: (definition string->real)

  blaming: program

   (assuming the contract is correct)

  at: eval:2.0

Conceptually, template macro variables are resolved before macro expansion. To avoid altering or introducing lexical scope, template macros selectively alter the input text, enabling the infiltration of quoted forms and other literal data.

> (begin-template ([$x 1] [$y 2] [$z !])
    (writeln (sub1 $x$y00))
    (writeln '($x-$y$z "$y-$x$z")))

1199

(1-2! "2-1!")

1.2 Expansion-Driven Template Generation

Template macros can be defined iteratively.

; a 10x10 identity matrix
(begin-template ()
  (list (for/template ([$row (in-range 10)])
          (vector (for/template ([$col (in-range 10)])
                    (if-template (= $row $col) 1 0))))))

The template, or template macro body, above expands into an expression that produces a list of vectors.

(list (vector 1 0 0 0 0 0 0 0 0 0)
      (vector 0 1 0 0 0 0 0 0 0 0)
      (vector 0 0 1 0 0 0 0 0 0 0)
      (vector 0 0 0 1 0 0 0 0 0 0)
      (vector 0 0 0 0 1 0 0 0 0 0)
      (vector 0 0 0 0 0 1 0 0 0 0)
      (vector 0 0 0 0 0 0 1 0 0 0)
      (vector 0 0 0 0 0 0 0 1 0 0)
      (vector 0 0 0 0 0 0 0 0 1 0)
      (vector 0 0 0 0 0 0 0 0 0 1))

Template macros can also escape to the expanding environment. The untemplate and untemplate-splicing forms evaluate an expression with syntax-local-eval and then inject the caller’s lexical context into any non-syntax return values. Template macro variables are still visible inside these forms.

> (define-template (slow-fibonaccis $n)
    (if-template (<= $n 2)
      (build-list $n (λ _ 1))
      (let ([fibs (slow-fibonaccis (untemplate (sub1 $n)))])
        (cons (+ (car fibs) (cadr fibs)) fibs))))

When $n is, say 5, slow-fibonaccis first expands to

(let ([fibs (slow-fibonaccis 4)])
  (cons (+ (car fibs) (cadr fibs)) fibs))

The fully expanded template is

(let ([fibs (let ([fibs (let ([fibs (build-list 2 (λ _ 1))])
                          (cons (+ (car fibs) (cadr fibs)) fibs))])
              (cons (+ (car fibs) (cadr fibs)) fibs))])
  (cons (+ (car fibs) (cadr fibs)) fibs))

The code above runs quickly, but recursively generating and expanding every branch takes a while. The fast-fibonaccis function below improves performance by calculating the whole series in one expansion step.

> (define-template (fast-fibonaccis $n)
    (if-template (<= $n 2)
      '(untemplate (build-list $n (λ _ 1)))
      '(untemplate (for/fold ([fibs '(1 1)])
                             ([_ (in-range (- $n 2))])
                     (cons (+ (car fibs) (cadr fibs)) fibs)))))
> (fast-fibonaccis 20)

'(6765 4181 2584 1597 987 610 377 233 144 89 55 34 21 13 8 5 3 2 1 1)

Inside a template, unsyntax and unsyntax-splicing are aliases for untemplate and untemplate-splicing, respectively, when they occur outside quasisyntax. In the code below, small-fast-fibonacis behaves identically to fast-fibonaccis.

> (define-template (small-fast-fibonaccis $n)
    (if-template (<= $n 2)
      '#,(build-list $n (λ _ 1))
      '#,(for/fold ([fibs '(1 1)])
                   ([_ (in-range (- $n 2))])
           (cons (+ (car fibs) (cadr fibs)) fibs))))
> (small-fast-fibonaccis 20)

'(6765 4181 2584 1597 987 610 377 233 144 89 55 34 21 13 8 5 3 2 1 1)

1.3 The ‘$’-Prefix Convention

Throughout this manual, the names of template macro variables start with a ‘$’. Although the template API imposes no such restriction on the names of template macro variables, poorly-chosen variable names can lead to strange compile-time errors.

> (begin-template ([e X]) (define e 123))

dXfinX: undefined;

 cannot reference an identifier before its definition

  in module: 'program

> (begin-template ([e X]) '(define e 123))

quotX: undefined;

 cannot reference an identifier before its definition

  in module: 'program

2 Construction

syntax

(template (var-id ...) form ...)

Produces a syntax transformer whose uses accept one argument per var-id and then substitutes them into the forms.

Example:
> (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 ...) form ...] ...)

Produces a syntax transformer procedure. Each [(var-id ...) form ...] clause is analogous to a single template procedure; applying the templates-generated procedure is the same as applying a procedure that corresponds to one of the clauses—the first procedure that accepts the given number of arguments. If no corresponding procedure accepts the given number of arguments, a syntax error is raised.

Example:
> (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:20.0: f: bad syntax

  in: (f one two three)

syntax

(untemplate expr)

syntax

(untemplate-splicing expr)

Escapes from an expanding template and replaces itself with the result of expr, an expression at phase level 1 relative to the surrounding context. Any result that is not a syntax object is converted to one with the caller’s lexical context, source location, and syntax properties.

Examples:
> (let-syntax ([x #'(+ 2 3)])
    (begin-template ()
      (+ 1 (untemplate (syntax-local-value #'x)))))

6

> (begin-template () '(1 (untemplate-splicing '(2 3))))

'(1 2 3)

As a notational convenience, unsyntax / unsyntax-splicing forms occurring inside a template and outside a quasisyntax are aliased to untemplate / untemplate-splicing.

Example:
> (let-syntax ([x #'(+ 2 3)])
    (begin-template ()
      (+ 1 #,(syntax-local-value #'x))))

6

> (begin-template () '(1 #,@'(2 3)))

'(1 2 3)

3 Binding Forms

syntax

(define-template (id var-id ...) form ...)

Creates a transformer binding of id to (template (var-id ...) form ...).

Example:
> (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

(let-template ([(id var-id ...) form ...] ...) body ...)

Creates a transformer binding of each id with (template (var-id ...) form ...), which is an expression at phase level 1 relative to the surrounding context. Each id is bound in the bodys, and not in other forms.

Example:
> (let-template ([(fwd $x $y) '$x$y]
                 [(rev $x $y) '$y$x])
    (list (fwd a b) (rev a b)))

'(ab ba)

syntax

(letrec-template ([(id var-id ...) form ...] ...) body ...)

Like let-template, except that each var-id is also bound within all remaining forms.

Example:
> (letrec-template
      ([(is-even? $n) (if-template (zero? $n) #t (is-odd? #,(sub1 $n)))]
       [(is-odd? $n) (not (is-even? $n))])
    (list (is-even? 10) (is-even? 11)))

'(#t #f)

syntax

(splicing-let-template ([(id var-id ...) form ...] ...) body ...)

syntax

(splicing-letrec-template ([(id var-id ...) form ...] ...) body ...)

Like let-template and letrec-template, except that in a definition context, the bodys are spliced into the enclosing definition context (in the same way as for begin-template).

Examples:
> (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
      ([(is-even? $n) (if-template (zero? $n) #t (is-odd? #,(sub1 $n)))]
       [(is-odd? $n) (not (is-even? $n))])
    (define is-11-even? (is-even? 11))
    (define is-10-even? (is-even? 10)))
> (list is-11-even? is-10-even?)

'(#f #t)

4 Sequence, Selection, Iteration

syntax

(begin-template ([var-id val-expr] ...) form ...)

(begin-template ([var-id val-expr] ...) expr ...)
Substitutes occurrences of var-ids with corresponding val-exprs in the forms or exprs comprising its body.

The first form applies when begin-template appears at the top level, at module level, or in an internal-definition position (before any expression in the internal-definition sequence). In that case, the begin-template form is equivalent to splicing the expanded forms into the enclosing context.

The second form applies for begin-template in an expression position. In that case, the expanded exprs take its place.

Examples:
> (begin-template ([$x a] [$y b])
    (define ($x-$y? obj)
      (equal? obj '($x $y))))
> (a-b? '(a b))

#t

> (a-b? '(c d))

#f

> (list (begin-template ([$x a]) '$x)
        (begin-template ([$x b]) '$x))

'(a b)

Non-identifier literals may also be created via template macro variable resolution.

Example:
> (begin-template ([$x #f] [$y 2])
    (list (not $x) (+ $y0 1)))

'(#t 21)

syntax

(begin0-template ([var-id val-expr] ...) expr ...)

Like begin-template, except the results of the first expr are the results of the begin0-template form.

Example:
> (begin0-template ([$x a] [$y b])
    (values '$x '$y)
    (displayln 'hi))

hi

'a

'b

syntax

(if-template test-expr then-expr else-expr)

Evaluates test-expr, which is an expression at phase level 1 relative to the surrounding context. If it produces any value other than #f, then then-expr takes its place. Otherwise, else-expr takes its place.

Examples:
> (if-template (positive? -5) (error "doesn't get here") 2)

2

> (if-template (positive? 5) 1 (error "doesn't get here"))

1

> (let-syntax ([x 'we-have-no-bananas])
    (if-template (syntax-local-value #'x) "yes" "no"))

"yes"

> (let-syntax ([x #f])
    (if-template (syntax-local-value #'x) "yes" "no"))

"no"

syntax

(cond-template [test-expr then-body ...] ... maybe-else-clause)

 
maybe-else-clause = 
  | [else then-body ...]
A clause that starts with else must be the last clause.

If no clauses are present, (void) takes its place.

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, (begin then-body ...) takes its place.

Examples:
> (cond-template)
> (cond-template [else 5])

5

> (let-syntax ([x #f] [y #t])
    (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 body ...)

Evaluates test-expr, which is an expression at phase level 1 relative to the surrounding context. If the result is not #f, then (begin body ...) takes its places. otherwise, (void) takes its place.

Examples:
> (let-syntax ([x #t])
    (when-template (positive? -5) (displayln 'hi))
    (when-template (syntax-local-value #'x)
      (display '|hey |)
      (display 'there)))

hey there

syntax

(unless-template test-expr body ...)

Equivalent to (when-template (not test-expr) body ...).

Examples:
> (let-syntax ([x #f])
    (unless-template (positive? 5) (displayln 'hi))
    (unless-template (syntax-local-value #'x)
      (display '|hey |)
      (display 'there)))

hey there

syntax

(for/template ([var-id seq-expr] ...) body ...)

Iteratively evaluates the bodys. The seq-exprs are evaluated left-to-right at phase 1, and each must produce a sequence whose elements are syntax objects or primitive values.

Example:
> (for/template ([$x (in-syntax #'(A B C))]
                 [$n (in-naturals)])
    (define $x (add1 $n)))
> (list A B C)

'(1 2 3)

Using for/template in an expression context inside a template is equivalent to splicing the expanded bodys into the enclosing context.

Example:
> (begin-template ()
    (list (for/template ([$n (in-range 9)]) $n)))

'(0 1 2 3 4 5 6 7 8)

Splicing is only possible when for/template occurs within another template. When used outside an enclosing template, the results are wrapped with begin.

Example:
> (list (for/template ([$n (in-range 9)]) $n))

'(8)

syntax

(for*/template ([var-id seq-expr] ...) body ...)

Like for/template, but with all of its sequence iterations nested.

Examples:
> (for*/template ([$x (in-syntax #'(A B C))]
                  [$n (in-range 3)])
    (define $x$n (add1 $n)))
> (list A0 A1 A2 B0 B1 B2 C0 C1 C2)

'(1 2 3 1 2 3 1 2 3)

> (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 Identifier Sequences

syntax

(define-template-ids id member-id ...)

Defines id as a list of identifiers for use with in-template-ids.

syntax

(in-template-ids id)

Produces a sequence whose elements are the successive identifiers bound to id by define-template-ids.

Example:
> (define-template-ids operators + - * /)
> (for/template ([$op (in-template-ids operators)])
    (displayln ($op 2 3)))

5

-1

6

2/3

6 Modules

In template/lang-test.rkt:

#lang template ($x)
 
(define $xs '($x $x $x))

syntax

(load-template-module id mod-path)

Binds id to the template macro provided by mod-path.

Example:
> (load-template-module tpl template/lang-test)
> (tpl a)
> as

'(a a a)

syntax

(#%module-begin (var-id ...) form ...)

Exports a binding of the-template to (template (var-id ...) form ...).

Example:
> (module my-template-mod template
    ($x)
    (define $xs '($x $x $x $x)))
> (require 'my-template-mod)
> (the-template b)
> bs

'(b b b b)