294 lines
11 KiB
Racket
294 lines
11 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require racket/require
|
|
scribble/example
|
|
"orig.rkt"
|
|
(for-label subtemplate
|
|
(only-in syntax/parse/experimental/template)
|
|
(subtract-in racket/base subtemplate)))
|
|
|
|
@title{Examples}
|
|
|
|
This section contains a few (somewhat artificial) examples on how to use
|
|
@racketmodname[subtemplate]. Most of the examples here would be more wisely
|
|
written as functions, and @racketmodname[syntax/parse] would otherwise be
|
|
sufficient with slightly more verbose code. The tools offered by
|
|
@racketmodname[subtemplate] are more useful for complex macros, where
|
|
boilerplate should be elided as much as possible to leave the true structure
|
|
of the macro visible.
|
|
|
|
@section{Automatically deriving identifiers using subscripts}
|
|
|
|
When an identifier @racket[yᵢ] is encountered in a template, it is
|
|
automatically derived from the corresponding @racket[xᵢ]. In the following
|
|
example, @racket[tempᵢ] is implicitly bound to
|
|
@racket[#'(a/temp b/temp c/temp)], without the need to call
|
|
@racket[generate-temporaries].
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(syntax-parse #'(a b c)
|
|
[(vᵢ …)
|
|
#'([tempᵢ vᵢ] …)])]
|
|
|
|
It is common in macros to save an expression in a temporary variable to avoid
|
|
executing it twice. The following example builds on the previous one to do so,
|
|
without the need to call @racket[generate-temporaries]. Note that the
|
|
temporary identifiers generated this way are hygienic: there will be no name
|
|
clashes with identifiers from the user, nor with identifiers directly created
|
|
by your macro.
|
|
|
|
@examples[
|
|
(require racket/require
|
|
(for-syntax (subtract-in racket/base subtemplate/override)
|
|
subtemplate/override))
|
|
(define-syntax sum
|
|
(syntax-parser
|
|
[(_ vᵢ …)
|
|
#'(let ([tempᵢ vᵢ] …)
|
|
(unless (integer? tempᵢ)
|
|
(printf "Warning: ~a should be an integer, got ~a.\n"
|
|
'vᵢ
|
|
tempᵢ))
|
|
…
|
|
(+ tempᵢ …))]))
|
|
(sum 1 2 3)
|
|
(sum 1 (begin (displayln "executed once") 2) 3)
|
|
(sum 1 (+ 3 0.14) 3)]
|
|
|
|
If you run out of unicode subscripts, characters following the last @racket[_]
|
|
are treated as the subscript:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(syntax-parse #'(a b c)
|
|
[(v_foo …)
|
|
#'([temp_foo v_foo] …)])]
|
|
|
|
@section{Automatically extracting plain values from syntax objects}
|
|
|
|
In most cases, you do not have to call @racket[syntax->datum] anymore, as
|
|
@racketmodname[subtemplate] implicitly extracts the value of syntax pattern
|
|
variables. Do not rely too much on this feature, though, as future versions
|
|
may require explicit escapement with a concise shorthand, like
|
|
@racket[,pattern-variable] or @RACKET[#,pattern-variable].
|
|
|
|
@examples[
|
|
#:escape UNSYNTAX
|
|
(require racket/require
|
|
(for-syntax (subtract-in racket/base subtemplate/override)
|
|
subtemplate/override))
|
|
(define-syntax nested
|
|
(syntax-parser
|
|
[(_ n v)
|
|
(if (> n 0) (code:comment "No need for syntax-e")
|
|
#`(list (nested #,(sub1 n) v)) (code:comment "No need for syntax-e")
|
|
#'v)]))
|
|
(nested 5 '(a b c))]
|
|
|
|
The implicit @racket[syntax->datum] also works on pattern variables which have
|
|
a non-zero ellipsis depth:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(syntax-parse #'(1 2 3 4 5)
|
|
[(v …)
|
|
(define sum (apply + v))
|
|
(if (> sum 10)
|
|
"foo"
|
|
"bar")])]
|
|
|
|
@section{Function application enhancements}
|
|
|
|
Why bother ourselves with @racket[apply]? Let's just write what we want:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(syntax-parse #'(1 2 3 4 5)
|
|
[(v …)
|
|
(if (> (+ v …) 10)
|
|
"foo"
|
|
"bar")])]
|
|
|
|
Ellipses work as you expect when used in expressions:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(define/syntax-parse ((vᵢⱼ …) …) #'((1 2 3 4) (5 6)))
|
|
(define/with-syntax (xₖ …) #'(a b c))
|
|
(+ vᵢⱼ … …)
|
|
(define average (/ (+ vᵢⱼ … …) (length (list vᵢⱼ … …))))
|
|
average
|
|
(max (min vᵢⱼ …) …)
|
|
(list vᵢⱼ … … xₖ …)
|
|
(list (list (+ vᵢⱼ 1) …) … (symbol->string xₖ) …)
|
|
(list (list vᵢⱼ …) … xₖ …)
|
|
(code:comment "Automatically derived symbols:")
|
|
(list (list yᵢⱼ …) …)
|
|
(list yₖ …)
|
|
(code:comment "Same ids as the yₖ ones above:")
|
|
#'(yₖ …)
|
|
]
|
|
|
|
Here is another trick with ellipses: @racket[((vᵢ …) …)] should normally call
|
|
@racket[1] with arguments @racket[2 3 4], and @racket[5] with the argument
|
|
@racket[6], and then call the result of the first with the result of the
|
|
second as an argument. Since in most cases this is not what you want, the
|
|
@racket[list] function is implicitly called when the second element of an
|
|
application form is an ellipsis (do not abuse it, the semantics are a bit at
|
|
odds with the usual ones in Racket and might be surprising for people reading
|
|
your code):
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(define/syntax-parse ((vᵢⱼ …) …) #'((1 2 3 4) (5 6)))
|
|
((vᵢⱼ …) …)
|
|
(vᵢⱼ … …)
|
|
(((+ vᵢⱼ 1000) …) …)
|
|
(code:comment "Automatically derived symbols:")
|
|
((yᵢⱼ …) …)
|
|
(yᵢⱼ … …)]
|
|
|
|
Ellipses surprisingly also work on @racket[define],
|
|
@racket[define/with-syntax] and @racket[define/syntax-parse]:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(define/syntax-parse ((v …) …) #'((1 2 3 4) (5 6)))
|
|
(define/syntax-parse (x …) #'("a" "b" "c"))
|
|
(begin
|
|
(define w (+ v 1)) … …
|
|
(define/syntax-parse y:id (string->symbol x)) …)
|
|
w
|
|
#'(y …)]
|
|
|
|
Since the trick is pulled off by a custom @racket[begin] form, provided by
|
|
@racketmodname[subtemplate], it will not work as expected at the REPL unless
|
|
you explicitly wrap the define and ellipses with a @racket[begin] form, as
|
|
done above. Within a module, however, this should work fine.
|
|
|
|
@section{Ellipsis-preserving @racket[unsyntax]}
|
|
|
|
Racket's @orig:syntax and @orig:template from
|
|
@racketmodname[syntax/parse/experimental/template] both forget the current
|
|
ellipsis position within an @racket[unsyntax] form. This makes it difficult to
|
|
perform simple changes to each element of a pattern variable under ellipses.
|
|
@racketmodname[syntax/parse/experimental/template] provides template
|
|
metafunctions, but they are unpractical for one-off small-scale alterations.
|
|
With @racket[subtemplate], @RACKET[#,e] and @RACKET[#,@e] both preserve the
|
|
current ellipsis position, meaning that uses of @racket[syntax],
|
|
@racket[quasisyntax], @racket[template] and so on within @racket[e] will use
|
|
the currently-focused portion of pattern variables under ellipses.
|
|
|
|
@examples[
|
|
#:escape UNSYNTAX
|
|
(require subtemplate/override racket/list)
|
|
(define sd syntax->datum)
|
|
(define/syntax-parse ((v …) …) #'((1 2 3 4) (5 6)))
|
|
(sd #`(foo #,(+ v …) …))
|
|
(code:comment "Quote, escape, re-quote, re-escape, re-quote:")
|
|
(sd #`(foo #,(cons (length (syntax->list #'(v …)))
|
|
#`(#,(add1 (syntax-e #'v)) …))
|
|
…))
|
|
(code:comment "Concise version of the above:")
|
|
(sd #`(foo (#,(length (v …)) #,(add1 v) …) …))
|
|
(sd #`(foo #,(length (syntax->list #'(v …))) …))
|
|
(sd #`(foo #,(length (list v …)) …))
|
|
(sd #`(foo (#,(add1 v) …) …))
|
|
(sd #`(foo #,(add1 v) … …))
|
|
(sd #`(foo #,@(range v) … …))]
|
|
|
|
It is still possible to get the traditional full-escape behaviour with
|
|
@RACKET[#,,e] instead of @racket[unsyntax], and @RACKET[#,@,e] or
|
|
@RACKET[#,,@e] instead of @racket[unsyntax-splicing]:
|
|
|
|
@examples[
|
|
#:escape UNSYNTAX
|
|
(require subtemplate/override racket/list syntax/stx)
|
|
(define sd syntax->datum)
|
|
(define/syntax-parse ((x v …) …) #'((10 1 2 3 4) (100 5 6)))
|
|
x
|
|
v
|
|
(sd #`(foo (x #,,#'(x …)) …))
|
|
(sd #`(foo (x #,,(stx-map (λ (x) (add1 (syntax-e x))) #'(x …))) …))
|
|
(sd #`(foo (x #,,(list (list (add1 v) …) …)) …))
|
|
(sd #`(foo (x #,,(((add1 v) …) …)) …))
|
|
(sd #`(foo (x #,,(stx-map (λ (x) (length (syntax->list x)))
|
|
#'((v …) …))) …))
|
|
(sd #`(foo (x #,,((length (v …)) …)) …))
|
|
(sd #`(foo ((v …) #,,((length (v …)) …)) …))
|
|
(sd #`(foo (x #,,@((length (v …)) …)) …))
|
|
(sd #`(foo (x #,@,(range (length (x …)))) …))
|
|
(sd #`(foo (v … #,,@((range (length (v …))) …)) …))]
|
|
|
|
@section{Splicing and conditional template elements}
|
|
|
|
The splicing form @racket[?@] as well as @racket[??] should be familiar to
|
|
users of @racketmodname[syntax/parse/experimental/template]. The
|
|
@racketmodname[subtemplate] library provides overridden versions which also
|
|
work outside of syntax templates, as well as a few extras:
|
|
|
|
@examples[
|
|
(require subtemplate/override)
|
|
(define/syntax-parse ({~optional {~or k:keyword b:boolean i:nat}}
|
|
{~and {~or (v …) s:str}} …)
|
|
#'(#:a-keyword (1 2 3 4) "foo" (5 6)))
|
|
(list (?? (+ v …)
|
|
(string-length s)) …)
|
|
(list (?? (?@ v …)
|
|
(string-length s)) …)
|
|
(list 'x (?@@ '(y y y) (?? (?@ (list 'c v …))) …) 'z)
|
|
(list (?if s "string" "list of numbers") …)
|
|
(?cond [k (list (?? (?@ 'there-was-a-keyword v …)) …)]
|
|
[b (list (?? (?@ 'there-was-a-boolean (?? v s) …)) …)]
|
|
[else (list (?? (?@ (?? i) v …)) …)])
|
|
(list (?attr k) (?attr b) (?attr i))
|
|
(?? k b i 'none)]
|
|
|
|
The @racket[?@@] splicing form performs two levels of unwrapping (it can be
|
|
understood as a way to perform @racket[(?@ (append elements …))]). The
|
|
@racket[(?if _condition _true _false)] is a generalisation of @racket[??],
|
|
which accepts a @racket[_condition] template, and produces the
|
|
@racket[_true]-template if there are no missing elements in the
|
|
@racket[_condition] (in the sense of @racket[~optional]), and produces
|
|
@racket[_false] otherwise. @racket[?cond] is a shorthand for a sequence of
|
|
nested @racket[?if] forms, and @racket[(?attr a)] returns a boolean indicating
|
|
the presence of the attribute (it is a shorthand for @racket[(?if a #t #f)]).
|
|
Finally, @racket[??] itself is not limited to two alternatives. When given a
|
|
single alternative, @racket[??] implicitly uses @racket[(?@)], i.e. the empty
|
|
splice, as the second alternative (this is the behaviour of the version from
|
|
@racketmodname[syntax/parse/experimental/template]). When two or more
|
|
alternatives are specified, each one is tried in turn, and the last one is
|
|
used as a fallback (i.e. an empty splice is @emph{not} implicitly added as a
|
|
last alternative when there are already two or more alternatives).
|
|
|
|
The @racket[?if] form is useful when one would want to write a @racket[??]
|
|
form, where the triggering condition should not appear in the left-hand-side
|
|
of @racket[??], for example when changing the generated code based on the
|
|
presence of a keyword passed to the macro:
|
|
|
|
@examples[
|
|
(require racket/require
|
|
(for-syntax (subtract-in racket/base subtemplate/override)
|
|
subtemplate/override))
|
|
(define-syntax my-sort
|
|
(syntax-parser
|
|
[(_ {~optional {~and reverse-kw #:reverse}} v …)
|
|
#'(sort (list v …) (?if reverse-kw > <))]))
|
|
(my-sort 3 2 1)
|
|
(my-sort #:reverse 3 2 1)]
|
|
|
|
Note that @racket[?@] and @racket[?@@] work on regular lists (but ellipses do
|
|
not), and they can splice multiple arguments into the surrounding function
|
|
call. One last application trick is the dotted tail argument, used as a
|
|
shorthand for @racket[apply]:
|
|
|
|
@examples[
|
|
(require subtemplate/override racket/function)
|
|
(define l '((1 2 3) (4 5 6)))
|
|
(vector 'a (?@ l) 'c)
|
|
(+ 0 (?@@ (?@@ l)) 7)
|
|
(vector 'a (?@@ (?@@ l)) 'c)
|
|
(+ 0 (?@@ . l) 7)
|
|
(vector 'a (?@@ . l) 'c)
|
|
(map + . l)] |