6.7.5 Case-lambda

R5RS’s rest arguments are indeed useful and very general, but they often aren’t the most appropriate or efficient means to get the job done. For example, lambda* is a much better solution to the optional argument problem than lambda with rest arguments.

Likewise, case-lambda works well for when you want one procedure to do double duty (or triple, or ...), without the penalty of consing a rest list.

For example:

(define (make-accum n)
  (case-lambda
    (() n)
    ((m) (set! n (+ n m)) n)))

(define a (make-accum 20))
(a) ⇒ 20
(a 10) ⇒ 30
(a) ⇒ 30

The value returned by a case-lambda form is a procedure which matches the number of actual arguments against the formals in the various clauses, in order. The first matching clause is selected, the corresponding values from the actual parameter list are bound to the variable names in the clauses and the body of the clause is evaluated. If no clause matches, an error is signalled.

The syntax of the case-lambda form is defined in the following EBNF grammar. Formals means a formal argument list just like with lambda (see Lambda: Basic Procedure Creation).

<case-lambda>
   --> (case-lambda <case-lambda-clause>*)
   --> (case-lambda <docstring> <case-lambda-clause>*)
<case-lambda-clause>
   --> (<formals> <definition-or-command>*)
<formals>
   --> (<identifier>*)
     | (<identifier>* . <identifier>)
     | <identifier>

Rest lists can be useful with case-lambda:

(define plus
  (case-lambda
    "Return the sum of all arguments."
    (() 0)
    ((a) a)
    ((a b) (+ a b))
    ((a b . rest) (apply plus (+ a b) rest))))
(plus 1 2 3) ⇒ 6

Also, for completeness. Guile defines case-lambda* as well, which is like case-lambda, except with lambda* clauses. A case-lambda* clause matches if the arguments fill the required arguments, but are not too many for the optional and/or rest arguments.

Keyword arguments are possible with case-lambda* as well, but they do not contribute to the “matching” behavior, and their interactions with required, optional, and rest arguments can be surprising.

For the purposes of case-lambda* (and of case-lambda, as a special case), a clause matches if it has enough required arguments, and not too many positional arguments. The required arguments are any arguments before the #:optional, #:key, and #:rest arguments. Positional arguments are the required arguments, together with the optional arguments.

In the absence of #:key or #:rest arguments, it’s easy to see how there could be too many positional arguments: you pass 5 arguments to a function that only takes 4 arguments, including optional arguments. If there is a #:rest argument, there can never be too many positional arguments: any application with enough required arguments for a clause will match that clause, even if there are also #:key arguments.

Otherwise, for applications to a clause with #:key arguments (and without a #:rest argument), a clause will match there only if there are enough required arguments and if the next argument after binding required and optional arguments, if any, is a keyword. For efficiency reasons, Guile is currently unable to include keyword arguments in the matching algorithm. Clauses match on positional arguments only, not by comparing a given keyword to the available set of keyword arguments that a function has.

Some examples follow.

(define f
  (case-lambda*
    ((a #:optional b) 'clause-1)
    ((a #:optional b #:key c) 'clause-2)
    ((a #:key d) 'clause-3)
    ((#:key e #:rest f) 'clause-4)))

(f) ⇒ clause-4
(f 1) ⇒ clause-1
(f) ⇒ clause-4
(f #:e 10) clause-1
(f 1 #:foo) clause-1
(f 1 #:c 2) clause-2
(f #:a #:b #:c #:d #:e) clause-4

;; clause-2 will match anything that clause-3 would match.
(f 1 #:d 2) ⇒ error: bad keyword args in clause 2

Don’t forget that the clauses are matched in order, and the first matching clause will be taken. This can result in a keyword being bound to a required argument, as in the case of f #:e 10.