subtemplate/scribblings/examples.scrbl
2017-05-06 03:39:32 +02:00

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)]