define2
1 Define and lambda
no-value
no-value?
lambda
λ
define
2 Wrapper functions
define-wrapper
3 Acknowledgements
7.9

define2

Laurent Orseau

1 Define and lambda

 (require define2) package: define2

There may be incompatibility with code that uses the keywords #:! and #:?.

The define2 collection redefines lambda and define in a (almost entirely) backward compatible way to provide the following functionalities:
  • 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.

Example: Mandatory #:! argument
> (define (make-fruits fruit #:! number)
    (make-list number fruit))
> (make-fruits 'apple #:number 4) ; Notice the keyword name

'(apple apple apple apple)

Example: Optional #:? argument
> (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)

Example: Pass-through #:? argument
; 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))

value

no-value : symbol?

procedure

(no-value? x)  boolean?

  x : any/c
no-value is an uninterned symbol representing the default value of pass-through arguments. no-value does not normally need to be used, but is provided for clarity and possibly for user enhancements.

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]
Like lambda and λ from racket/base, but with support for #:! mandatory keyword arguments and #:? optional keyword arguments.

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).

syntax

(define id expr)

(define (head args) body ...+)
Like define from racket/base, but uses lambda from define2 instead. Also supports the curried form.

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
arg ... maybe-rest and keyword-arg ... are arguments as for lambda, but keyword-arg ... are restricted to keyword arguments.

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.

More concretely (supposing that bar is already defined elsewhere),

(define-wrapper (foo (bar a #:? [b 'b])))

is equivalent to
(define (foo a #:? [b 'b])
  (bar a #:b b))
and
(define-wrapper (foo (bar a #:? [b 'b])
                     #:c c)
  (set! a (+ a c)))
is equivalent to
(define (foo a #:? [b 'b] #:c c)
  (set! a (+ a c))
  (bar a #:b b))
and
(define-wrapper (foo (bar a #:? [b 'b])
                     #:c c)
  #:call-wrapped bar-wrapped
  (set! a (+ a c))
  (define res (bar-wrapped))
  (displayln res)
  res)
is equivalent to
(define (foo a #:? [b 'b] #:c c)
  (set! a (+ a c))
  (define res (bar a #:b b))
  (displayln res)
  res)

Note: Be careful to not use pass-through arguments if the corresponding argument in wrapped-fun is not an optional #:? argument.

For example, this fails:
> (define-wrapper (my-sort (sort l <? #:? key)))
> (my-sort '(1 4 2) <)

sort: contract violation

  expected: (any/c . -> . any/c)

  given: 'no-value

But this is fine:
> (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.