1.2.2 Optional Keyword Arguments
This section explains how to write a macro that accepts (simple) optional keyword arguments. We use the example mycond, which is like Racket’s cond except that it takes an optional keyword argument that controls what happens if none of the clauses match.
Optional keyword arguments are supported via head patterns. Unlike normal patterns, which match one term, head patterns can match a variable number of subterms in a list. Some important head-pattern forms are ~seq, ~or*, and ~optional.
Here’s one way to do it:
> (define-syntax (mycond stx) (syntax-parse stx [(mycond (~or* (~seq #:error-on-fallthrough who:expr) (~seq)) clause ...) (with-syntax ([error? (if (attribute who) #'#t #'#f)] [who (or (attribute who) #'#f)]) #'(mycond* error? who clause ...))]))
> (define-syntax mycond* (syntax-rules () [(mycond error? who [question answer] . clauses) (if question answer (mycond* error? who . clauses))] [(mycond #t who) (error who "no clauses matched")] [(mycond #f _) (void)]))
We cannot simply write #'who in the macro’s right-hand side, because the who attribute does not receive a value if the keyword argument is omitted. Instead we must first check the attribute using (attribute who), which produces #f if matching did not assign a value to the attribute.
> (mycond [(even? 13) 'blue] [(odd? 4) 'red])
> (mycond #:error-on-fallthrough 'myfun [(even? 13) 'blue] [(odd? 4) 'red])
myfun: no clauses matched
220.127.116.11 Optional Arguments with ~?
The ~? template form provides a compact alternative to explicitly testing attribute values. Here’s one way to do it:
> (define-syntax (mycond stx) (syntax-parse stx [(mycond (~optional (~seq #:error-on-fallthrough who:expr)) clause ...) #'(mycond* (~? (~@ #t who) (~@ #f #f)) clause ...)]))
If who matched, then the ~? subtemplate splices in the two terms #t who into the enclosing template (~@ is the template splicing form). Otherwise, it splices in #f #f.
Here’s an alternative definition that re-uses Racket’s cond macro:
> (define-syntax (mycond stx) (syntax-parse stx [(mycond (~optional (~seq #:error-on-fallthrough who:expr)) clause ...) #'(cond clause ... (~? [else (error who "no clause matched")] (~@)))]))
In this version, we optionally insert an else clause at the end to signal the error; otherwise we use cond’s fall-through behavior (that is, returning (void)).
If the second subtemplate of a ~? template is
18.104.22.168 Optional Arguments with define-splicing-syntax-class
Yet another way is to introduce a splicing syntax class, which is like an ordinary syntax class but for head patterns.
> (define-syntax (mycond stx) (define-splicing-syntax-class maybe-fallthrough-option (pattern (~seq #:error-on-fallthrough who:expr) #:with error? #'#t) (pattern (~seq) #:with error? #'#f #:with who #'#f)) (syntax-parse stx [(mycond fo:maybe-fallthrough-option clause ...) #'(mycond* fo.error? fo.who clause ...)]))
Defining a splicing syntax class also makes it easy to eliminate the case analysis we did before using attribute by defining error? and who as attributes within both of the syntax class’s variants. This is possible to do in the inline pattern version too, using ~and and ~parse, but it is less convenient. Splicing syntax classes also closely parallel the style of grammars in macro documentation.