A procedure's arity is the number of arguments it takes. You'll
probably have noticed that while some of Scheme's built-in procedures
always take the same number of arguments (for instance, the arity of the
cons procedure is 2, and the arity of the predicate char-uppercase? is 1), others have variable arity -- that is,
the number of arguments in a call can vary. Such Scheme procedures as
list, +, or string-append can take any number of
arguments.
Still 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 the number of arguments varies from one call to another.
It is possible for the programmer to define new variable-arity procedures
in Scheme, by using alternate forms of the lambda-expression.
In all of the programmer-defined procedures that we have seen so far, the
keyword lambda has been followed by a list of parameters -- names
for the values that will be supplied by the caller when the procedure is
invoked. If, instead, what follows lambda is a single identifier --
not a list, but a simple identifier, not enclosed in parentheses -- then
the procedure denoted by the lambda-expression will accept any
number of arguments, and the identifier following lambda will name a
list of all the arguments supplied in the call.
As an example, let's develop a procedure called slam that
works like a combination of cons and append: It
takes any number of arguments, some or all of which may be lists, and
concatenates them; but when it finds an argument that is not a list, it
just puts it in as an element of the list it returns.
Especially since the behavior I've described is a little weird, we should begin by writing a lot of sample calls, making sure that we know what we want to have happen in each case:
> (slam) ; no arguments: result should be null () > (slam 'alpha) ; one argument, not a list (alpha) > (slam '(beta)) ; one argument, a list (beta) > (slam 'gamma 'delta 'epsilon) ; non-lists only (gamma delta epsilon) > (slam '(zeta) '(eta theta) '() '(iota kappa mu nu)) ; lists only (zeta eta theta iota kappa mu nu) > (slam 'omicron '(pi rho) 'sigma 'tau '(upsilon)) (omicron pi rho sigma tau upsilon)
The following examples illustrate the point that only the top-level lists are ``opened up'' to get elements for the result list:
> (slam 1 2 '(3 4) '((5 6) (7 8)) '(((9 10) (11 12)))) (1 2 3 4 (5 6) (7 8) ((9 10) (11 12))) > (slam 1 '(2) 3 '((4)) 5 '(((6)))) (1 2 3 (4) 5 ((6))) > (slam 1 '() 2 '() 3) (1 2 3) > (slam 1 '(()) 2 '((())) 3) (1 () 2 (()) 3) > (slam '() '() '() '() '()) () > (slam '() '(()) '(())) '(((())))) (() (()) ((())))
Since slam can take any number of arguments, including none,
the lambda-expression that we write should have an identifier
after the lambda, not a list of identifiers:
(define slam
(lambda arguments
...))
When slam is actually invoked, the arguments are assembled into a
list, and this list is the value of arguments inside the lambda-expression. Now it's a straightforward list recursion: If arguments is empty, we return '(). Otherwise, we issue a recursive
call to slam the cdr of arguments, then either append or cons the
car of arguments on the front of the result, depending on whether
the car of arguments is a list or not.
Since we have abstracted list recursion into the higher-order procedure
fold-list, the body of the
lambda-expression is easy to write:
;;; slam: assemble a list from any number of given values, ;;; extracting top-level elements from any of them that are lists ;;; Givens: ;;; Some number of values, collectively called ARGUMENTS. ;;; Result: ;;; LS, a list. ;;; Preconditions: ;;; None. ;;; Postcondition: ;;; The elements of LS are precisely the values among ;;; ARGUMENTS that are not lists and the top-level elements ;;; of the values among ARGUMENTS that are lists. (define slam (lambda arguments ((fold-list '() (lambda (new base) (if (list? new) (append new base) (cons new base)))) arguments)))
It's a little easier to define slam in this way, using the
higher-order procedure than to make the recursion explicit. For
comparison, here's what slam looks like with explicit recursion
instead of the call to fold-list:
(define slam
(lambda arguments
(if (null? arguments)
'()
(let ((new (car arguments))
(base (apply slam (cdr arguments))))
(if (list? new)
(append new base)
(cons new base))))))
The tricky part here is to think of writing (apply slam (cdr
arguments)) rather than simply (slam (cdr arguments)). Remember,
the variable-arity construction has the effect of collecting all of the
arguments into a list. You need apply to make sure that, in effect,
the elements of (cdr arguments) are broken out again before the
recursive call is made.
If the programmer wishes to require some fixed minimum number of arguments
while permitting (but not requiring) additional ones, she can use yet
another form of the lambda-expression, in which a dot is
placed between the last two identifiers in the parameter list. All the
identifiers to the left of this dot correspond to individual required
arguments. The identifier to the right of the dot designates the list of
all of the remaining arguments, the ones that are optional.
For instance, let's look at the set-difference procedure, which
takes one or more lists l1, l2, ..., ln as arguments and returns a
list containing all of the elements of l1 that are
not also elements of any of l2, ...,
ln. The set-difference procedure
allows the caller to supply any number of lists of values to be ``pruned
out'' of an initial list:
> (set-difference '(a b c)) (a b c) > (set-difference '(a b c d e) '()) (a b c d e) > (set-difference '(a b c d e) '(a c)) (b d e) > (set-difference '(a b c d) '(b e h)) (a c d) > (set-difference '() '(a b c d)) () > (set-difference '(a b c) '(c e b)) (a) > (set-difference '(a b c) '(c a k b d)) () > (set-difference '(a b c d e) '(a f) '(e b h)) (c d) > (set-difference '(a b c d e f) '(b g) '() '(h e j p t) '(c b)) (a d f)
Here's the definition:
;;; set-difference: construct and return a list formed by ;;; removing the elements of any number of given lists from ;;; a given initial list ;;; Givens: ;;; INITIAL, a list. ;;; Any number of lists, collectively called OTHERS. ;;; Result: ;;; DIFFERENCE, a list. ;;; Preconditions: ;;; None. ;;; Postconditions: ;;; The elements of DIFFERENCE are exactly those elements ;;; of INITIAL that are not also elements of any of OTHERS. (define set-difference (lambda (initial . others) ((fold-list initial (lambda (new recursive-result) ((remove (right-section member new)) recursive-result))) others)))
In English: Call the initial list initial and collect all of the
other arguments into a list called others. Using list recursion,
fold over others: In the base case, where others is null,
just return initial. In any other case, separate others into
its car and its cdr, and issue a recursive call to deal with the cdr --
that is, prune out of initial all of the elements of elements of the
cdr. From the result of this recursive call, remove any value that is a
member of the car and return the result.
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.
The principal author of this reading is Professor Henry Walker. I am also indebted to Professor Ben Gum for his contributions to its development.