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, they can take any number of arguments. All one can say about the
arity of a Scheme procedure such as list, +, or
string-append is that it is some non-negative integer.
Still other Scheme procedures, such as map and
display, 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,'' and the arity of
display is ``1 or 2.'' These procedures, too, are said to
have variable arity, because their arity 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.
Here's a simple example: We'll define a display-line
procedure that takes any number of arguments and prints out each one (by
applying the display procedure to it), then terminates the
output line (by invoking newline). Note that in the
lambda-expression, the identifier arguments
denotes a list of all the items to be printed:
(define display-line
(lambda arguments
(let kernel ((rest arguments))
(if (null? rest)
(newline)
(begin
(display (car rest))
(kernel (cdr rest)))))))
When display-line is invoked, however, the caller does not
assemble the items to be printed into a list, but just supplies them as
arguments:
> (display-line "+--" "Here is a string!" "--+") +--Here is a string!--+ > (display-line "ratio = " 35/15) ratio = 7/3
Try out some other calls to display-line to check what it
prints. For example, try the following:
(display-line "going" "going" "gone") (display-line "countdown:" 5 4 3 2 1 "done") (display-line) ;; apply display-line to no arguments
Explain your results.
The current version of display-line prints all text together
without spaces. Modify the code, so that one space is printed between any
two adjacent values supplied as arguments to display-line.
For instance, after your modifications, the output from the first call to
display-line above should be
+-- Here is a string! --+
Define and test a procedure named call-arity that takes any
number of arguments and returns the number of arguments it received
(ignoring their values):
> (call-arity 'a #\b "c" '(d)) 4 > (call-arity 0.0) 1 > (call-arity) 0
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 single 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, we can define a procedure called
display-separated-line that always takes at least one
argument, separator, but may take any number of additional
arguments. Display-separated-line will print out each of the
additional arguments (by invoking display) and terminate the
line, just as display-line does, but with the difference that
a copy of separator will be displayed between any two of the
remaining values. Here is some sample output:
> (display-separated-line "..." "going" "going" "gone") going...going...gone > (display-separated-line ":-" 5 4 3 2 1 'done) 5:-4:-3:-2:-1:-done > (display-separated-line #\space "+--" "Here is a string!" "--+") +-- Here is a string! --+ > (display-separated-line (integer->char 9) 1997 'foo 'wombat 'quux) 1997 foo wombat quux ; (INTEGER->CHAR 9) is the tab character.
And here is the definition of the procedure:
(define display-separated-line
(lambda (separator . arguments)
(if (null? arguments)
(newline)
(let kernel ((rest arguments))
(display (car rest))
(if (null? (cdr rest))
(newline)
(begin
(display separator)
(kernel (cdr rest))))))))
As another example, 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. For example, the value of
(set-difference (list 'a 'b 'c 'd 'e 'f 'g)
(list 'a 'e) (list 'b) (list 'a 'f 'h))
is (list 'c 'd 'g), because the elements 'c,
'd, and 'g of the first argument are not elements
of any of the subsequent list. The set-difference procedure
allows the caller to supply any number of lists of values to be ``pruned
out'' of the initial list.
Here's the definition:
(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.
What happens if you invoke display-separated-line without
giving it any arguments? What happens when only one argument is given?
Define and test a procedure clicker that takes one or more
arguments, of which the first must be an integer and each of the others must
be either the symbol 'up or the symbol 'down.
Clicker should start from the given integer, add 1 for
each 'up argument, subtract 1 for each 'down
argument, and return the result:
> (clicker 17 'up 'up) 19 > (clicker -12 'down 'up 'down 'down 'down) -15 > (clicker 100) 100
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.
Modify the definition of the display-separated-line procedure
so that it takes an output port as its first (required) argument, the
separator as its second (required) argument, and any number of values to
be printed as additional (optional) arguments. The procedure should write
the line, with separators, to the specified output port rather than to the
interaction window.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/variable-arity.xhtml
created March 22, 1997
last revised March 17, 2000
Henry Walker (walker@cs.grinnell.edu) and John David Stone (stone@cs.grinnell.edu)