8.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 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 write (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

There’s a simpler way of writing the ~or pattern above:
  (~optional (~seq #:error-on-fallthrough who:expr))

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-fallthough 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, just less convenient.) Splicing syntax classes also closely parallel the style of grammars in macro documentation.