define2
1 Define and lambda
(require define2) | package: define2 |
There may be incompatibility with code that uses the keywords #:! and #:?.
a shortcut definition for keyword arguments to avoid the ubiquitous #:some-arg some-arg repetition,
a pass-through mechanism for optional keyword arguments to propagate default values without having to know them.
> (define (make-fruits fruit #:! number) (make-list number fruit)) > (make-fruits 'apple #:number 4) ; Notice the keyword name '(apple apple apple apple)
> (define (make-fruits2 fruit #:? [number 3]) (make-list number fruit)) > (make-fruits2 'pear) '(pear pear pear)
> (make-fruits2 'pear #:number 4) '(pear pear pear pear)
; Let's write a function that uses `make-fruits2` without changing ; the default value for `number`—whatever value this is.
> (define (make-two-fruits fruit1 fruit2 #:? number) (list (make-fruits2 fruit1 #:number number) (make-fruits2 fruit2 #:number number))) > (make-two-fruits 'apple 'banana) '((apple apple apple) (banana banana banana))
> (make-two-fruits 'apple 'banana #:number 2) '((apple apple) (banana banana))
syntax
(lambda args body ...+)
syntax
(λ args body ...+)
args = (pos-id ... [opt-id opt-expr] ... kw-arg ...) | (pos-id ... [opt-id opt-expr] ... kw-arg ... . rest-id) | rest-id kw-arg = #:! id | #:? id | #:? [id expr] | keyword id | keyword [id expr]
An argument of the form #:! name is equivalent to #:name name. An argument of the form #:? [name val] is equivalent to #:name [name val] but binds name to val only if name is no-value. An argument of the form #:? name is equivalent to #:name [name no-value].
This means in particular that (lambda (#:a the-a #:! a) ...) is a syntax error (duplicate argument keyword), as well as (lambda (#:a the-a #:! the-a) ...) (duplicate argument identifier).
2 Wrapper functions
(require define2/define-wrapper) | package: define2 |
Writing wrapper functions is already simplified with the new define thanks to pass-through optional arguments, but there can still be some verbosity left due to having to repeat the argument names. define-wrapper helps with this by passing the arguments to the wrapped function automatically.
syntax
(define-wrapper (fun [wrapped-fun arg ... maybe-rest] keyword-arg ...) maybe-call-wrapped body ...)
maybe-call-wrapped =
| #:call-wrapped call-wrapped-id
The resulting function fun takes as input all the arguments arg ... keyword-arg ... maybe-rest. Only the arguments arg ... maybe-rest are forwarded to the call to wrapped-fun. The function wrapped-fun must be defined elsewhere.
If call-wrapped-id is not provided then wrapped-fun is called in tail-position; otherwise it should be called as (call-wrapped-id) somewhere in body ..., and this calls wrapped-fun with the arguments arg ... maybe-rest.
(define-wrapper (foo (bar a #:? [b 'b])))
(define (foo a #:? [b 'b]) (bar a #:b b))
(define-wrapper (foo (bar a #:? [b 'b]) #:c c) (set! a (+ a c)))
(define (foo a #:? [b 'b] #:c c) (set! a (+ a c)) (bar a #:b b))
(define-wrapper (foo (bar a #:? [b 'b]) #:c c) #:call-wrapped bar-wrapped (set! a (+ a c)) (define res (bar-wrapped)) (displayln res) res)
> (define-wrapper (my-sort (sort l <? #:? key))) > (my-sort '(1 4 2) <) sort: contract violation
expected: (any/c . -> . any/c)
given: 'no-value
> (define-wrapper (my-sort2 (sort l <? #:? [key values]))) > (my-sort2 '(1 4 2) <) '(1 2 4)
3 Acknowledgements
Thanks to Ross Angle, Sorawee Porncharoenwase, Jack Firth, Jens-Axel Soegaard, Sam Tobin-Hochstadt, Greg Hendershott, Bogdan Popa, and Leif Anderson for their help.