syntax-objects / Summer2021

Syntax Parse Bee 2021
11 stars 3 forks source link

Flaggable #%app: #%app that understands flags #14

Open sorawee opened 3 years ago

sorawee commented 3 years ago

Many functions accept a lot of optional "boolean" keyword arguments. These arguments are known as flags. As an example:

(define (string-trim s #:left? [left? #f] #:right? [#:right? #f])
  (list s left? right?))

has two flags: #:left? and #:right?.

The function then can be called with:

(string-trim " 1 2 3 " #:left? #t #:right? #t)

Flaggable #%app allows users to instead write:

(string-trim " 1 2 3 " #:left? #:right?)

That is, a keyword argument that doesn't have a "value" will default the value to #t. Explicit "value" is still supported.

This does come at a cost: all keyword arguments must be specified after positional arguments to avoid ambiguity. Without this restriction, it's hard to tell whether:

(f #:a 1)

is meant to be itself or:

(f 1 #:a #t)

Macro

#lang racket 

(require syntax/parse/define
         (only-in racket [#%app racket:#%app]))

(begin-for-syntax
  (define-splicing-syntax-class arg/keyword
    #:attributes (k v)
    ;; first case: something like #:a 1
    (pattern {~seq k:keyword v:expr})
    ;; second case: something like #:a.
    (pattern {~seq k:keyword}
             #:with v #'#t)))

(define-syntax-parse-rule (#%app f arg/no-keyword:expr ... arg/keyword:arg/keyword ...)
  (racket:#%app f arg/no-keyword ... {~@ arg/keyword.k arg/keyword.v} ...))

Note: inspired by https://www.reddit.com/r/Racket/comments/oytknk/keyword_arguments_without_values/h7w67dd/, though I had thought of doing it before, too.

Example usage:

(define (f c #:a [a #f] #:b [b #f])
  (list c a b))

(f 0 #:a #:b)       ;=> '(0 #t #t)
(f 0 #:a)           ;=> '(0 #t #f)
(f 0 #:b)           ;=> '(0 #f #t)
(f 0 #:a 10 #:b)    ;=> '(0 10 #t)
(f 0 #:a #:b 20)    ;=> '(0 #t 20)
(f 0 #:a 10 #:b 20) ;=> '(0 10 20)
(f 0)               ;=> '(0 #f #f)
#;(f #:a 0 1)
; #%app: expected keyword
;   parsing context: 
;    while parsing arg/keyword in: 1

Before code

(f 0 #:a #t #:b #t) ;=> '(0 #t #t)
(f 0 #:a #t)        ;=> '(0 #t #f)
(f 0 #:b #t)        ;=> '(0 #f #t)
(f 0 #:a 10 #:b #t) ;=> '(0 10 #t)
(f 0 #:a #t #:b 20) ;=> '(0 #t 20)
(f 0 #:a 10 #:b 20) ;=> '(0 10 20)
(f 0)               ;=> '(0 #f #f)

Licence

MIT / CC-BY 4.0