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))
> (define (make-fruits3 fruit #:number [a-number 3]) (make-list a-number fruit))
> (define (make-fruits4 fruit #:? number) (make-fruits3 fruit #:number number)) > (make-fruits4 'clementine) '(clementine clementine clementine)
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 Compile-time error checking
In standard Racket, when a function is defined with define, and is later called with the wrong number of arguments or with the wrong keywords, an error is signalled only at run time, when the function is called.
Instead, using the define form provided by define2 signals an error at compile-time.
Thus, such errors are caught much earlier than with standard Racket, for example with raco make. Furthermore, since DrRacket runs background expansion, such errors can be caught as early as the are written.
> (define (foo bar [baz 'b] #:fizz fizz #:buzz [buzz #t]) #true) > (foo) eval:17:0: foo: missing mandatory positional arguments
header: (foo bar (baz) #:fizz (#:buzz))
at: (foo)
in: (foo)
> (foo 'a 'b 'c #:fizz 'f) eval:18:0: foo: too many positional arguments
header: (foo bar (baz) #:fizz (#:buzz))
at: (foo 'a 'b 'c #:fizz 'f)
in: (foo 'a 'b 'c #:fizz 'f)
> (foo 'a) eval:19:0: foo: missing keywords
header: (foo bar (baz) #:fizz (#:buzz))
at: (foo 'a)
in: (foo 'a)
> (foo 'a #:fizz 'f #:beurre 'b) eval:20:0: foo: unknown keyword
header: (foo bar (baz) #:fizz (#:buzz))
at: #:beurre
in: (foo 'a #:fizz 'f #:beurre 'b)
For the last error, DrRacket even highlights the wrong keyword.
4 Acknowledgements
Thanks to Ross Angle, Sorawee Porncharoenwase, Jack Firth, Jens-Axel Soegaard, Sam Tobin-Hochstadt, Greg Hendershott, Bogdan Popa, Matthew Flatt, Robby Findler, and Leif Anderson for their help.