A procedure's arity is the number of arguments it takes. In each of the procedures we have defined so far, we have started with the framework
(lambda (parameter1 parameter2 ... parameterN) ... )
That is, we specify exactly what parameters a procedure will have;
the number of these parameters (N) is fixed. For example, the arity of
the cons procedure is 2, and the arity of the predicate
char-uppercase? is 1.
However, not all of the procedures we have encountered have this property.
For example, Scheme's procedure's list, +, and
string-append allow any number of parameters. We say such
procedures have variable arity. Other Scheme procedures, such
as map require at least a certain number of arguments,
but will accept one or more additional arguments if they are provided.
For example, the arity of map is "2 or more." These
procedures, too, are said to have variable arity, because their arity varies
from one call to another. In what follows, we explore how these procedures
of variable arity can be defined.
As a first example, consider how we might define a procedure to add an
unlimited number of values. Of course, the procedure +
is already built into Scheme, but we consider how to write
add ourselves assuming that + applies just
to two numbers. Thus, add should return the
following results:
| Expression | Value Returned |
|---|---|
(add 3 1) | 4 |
(add 3 1 4 1 5) | 14 |
(add) | 0 |
The following procedure illustrates how this procedure add
could be written:
;;; add: compute the sum of the values that follow
;;; Givens:
;;; Some number of values, collectively called VALUES
;;; Result:
;;; SUM, a number
;;; Preconditions:
;;; all VALUES are numbers
;;; Postcondition:
;;; SUM is the result of adding the numbers in VALUES
(define add
(lambda values
(let loop ((lst values))
(if (null? lst)
0
(+ (car lst) (loop (cdr lst)))
)
)
)
)
In this approach, note that the symbol values after
lambda is not in parentheses. Scheme interprets this syntax
to indicate that all parameters to the procedure should be grouped together
in a list. Thus, in the procedure call (sum 3 1 4 1 5),
Scheme interprets the parameter values to be the list
(3 1 4 1 5).
Next, consider the task of editing a sequence of numbers, removing negative
numbers and those larger than a specified maximum. In this case, a first
parameter will be required to state the specified maximum, and all
subsequent values will be scanned to determine which are in range. The
following table illustrates how this filter-1 procedure should
work:
| Expression | Value Returned |
|---|---|
(filter-1 3 6 2 -1) | (2) |
(filter-1 100 -10 -1 0 1 10 100 1000) |
(0 1 10 100) |
(filter-1 100) |
() |
(filter-1) |
Error: parameter missing |
In this case, filter-1 only makes sense if it contains a first
parameter, but we cannot anticipate how many parameters will occur after
that. Another form of lambda expressions allows us to bind
initial parameters to specific symbols, with any remaining parameters
collected together in a list. This is illustrated in the following code:
;;; filter-1: filter negatives and large numbers from a number sequence
;;; Givens:
;;; A MAXIMUM allowed value, followed by
;;; Some number of values, collectively called VALUES
;;; Result:
;;; LS: a list of numbers
;;; Preconditions:
;;; MAXIMUM and all VALUES are numbers
;;; Postcondition:
;;; LS contains all numbers in VALUES, provided the numbers are
;;; non-negative and do not exceed MAXIMUM. The order of numbers
;;; on LS is the same as the order in VALUES.
(define filter-1
(lambda (maximum . values)
(let loop ((lst values) (result-so-far '()))
(cond ((null? lst) (reverse result-so-far))
((<= 0 (car lst) maximum)
(loop (cdr lst) (cons (car lst) result-so-far)))
(else (loop (cdr lst) result-so-far))
)
)
)
)
Here, (maximum . values) follows lambda. In this
context, the first parameter is bound to maximum,while all
remaining parameters are placed on a list and bound to values.
The dot notation can be used to specify any number of initial values. Thus, a parameter list of the form
(first-value second-value . remaining-values)
indicates that the first two arguments are required, while additional
arguments will be collected into a list named
remaining-values.
As an example, the following refinement of the previous filtering procedures asks the user to specify both the minimum and maximum values for filtering subsequent values.
;;; filter-2: filter values below a minimum or above a maximum from a number sequence
;;; Givens:
;;; A MINIMUM and a MAXIMUM allowed value, followed by
;;; Some number of values, collectively called VALUES
;;; Result:
;;; LS: a list of numbers
;;; Preconditions:
;;; MINIMUM, MAXIMUM, and all VALUES are numbers
;;; Postcondition:
;;; LS contains all numbers in VALUES, provided the numbers are
;;; not below MINIMUM do not exceed MAXIMUM. The order of numbers
;;; on LS is the same as the order in VALUES.
(define filter-2
(lambda (minimum maximum . values)
(let loop ((lst values) (result-so-far '()))
(cond ((null? lst) (reverse result-so-far))
((<= minimum (car lst) maximum)
(loop (cdr lst) (cons (car lst) result-so-far)))
(else (loop (cdr lst) result-so-far))
)
)
)
)
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~walker/courses/151.sp04/readings/variable-arity.xhtml
created March 22, 1997
last revised March 9, 2004
Henry Walker (walker@cs.grinnell.edu), John David Stone (stone@cs.grinnell.edu), and Ben Gum (gum@cs.grinnell.edu)