In Scheme, you can easily write procedures that can take a variable number of arguments. Technically, the number of arguments a procedure accepts is called its arity, and we call a procedure that accepts a variable number a variable arity procedure.(4)
One way to write a variable arity procedure is to use an argument declaration form that consists of a single argument name, rather than a parenthesized sequence of argument names. This tells Scheme that the procedure's actual arguments should be packaged up as a list when the procedure is entered, and the procedure will have a single argument that points to this list of argument values.
For example, we could write a procedure that takes any number of arguments and displays the list of actual arguments passed to the procedure.
(define (display-all . args) (display args))
Here the argument variable args receives the list of all arguments, and we use display to display this list. Now if we call the procedure like this
Scheme>(display-all 'foo 3 'bar) (foo 3 bar)
the argument variable args
will be bound and initialized with
a list (foo 3 bar)
, which will be passed as the sole argument
to display
. Once inside the procedure, there's nothing
special about this argument variable args
---it just happens to
hold the list of arguments that were passed.
This works for lambda
expressions as well. We could define
display-all
using an equivalent plain variable definition
whose initial value is the result of an explicit lambda expression:
(define display-all (lambda args (display args)))
(Notice that for this (plain lambda
) version, we just
used args
as the argument specification, not (args)
.
If we just use an identifer, rather than a parenthesized sequence
of identifiers, Scheme packages up all of the actual arguments
to the procedure as a list and hands that to display-all
as one argument variable. This looks a little different from
the define
version, but it's the same idea--we're using
the variable args
to "stand for" a sequence of argument
values, which scheme represents as a list.)
Often, you write procedures that take a certain number of normal (required) arguments, but can take more. When you pass a procedure more arguments than it requires, Scheme packages up the extra arguments in a list, called a rest list.
Scheme allows you to express this by writing a mostly normal-looking parenthesized sequence of argument names, followed by a dot and the name of the argument to receive the list of the remaining arguments. (If no extra arguments are passed, this argument variable will receive the empty list.)
For example, suppose we want our display-all
procedure to
accept at least one argument, display it, and then display the
list of any remaining arguments. We could write it like this:
(define (display-all first . rest) (display first) (display rest))
This allows us to declare that the procedure's first argument
is required, and give it a name of its own.
The dot notation is similar to the dot notation for improper
lists, and is used to suggest that the that variable after the
dot refers to the "rest" of the actual
arguments.\footnote{Consider an improper list (a b . c)
.
Here the first element of the list is a, the cadr
of the
list is b
, and the rest of the list beyond that
(the cddr
) is just c
. If we write the argument
declarations of a procedure in this way, e.g.,
(lambda (a b . c) ...)
, we think of the formal parameter
a
as "standing for" the first actual argument value, the
formal parameter b
as standing for the second actual
argument value, and the formal parameter c
as standing
for the rest of the actual argument values.}
One common application of variable arity is to allow optional
arguments with default values. For example we can define
a procedure foo
which takes two required arguments and a
third, optional argument. We would like to use a default
value for the optional argument, say #f
, if the optional
argument is not actually passed.
(define (foo a b . rest)
(let ((c (if (null? rest) ; if no extra argument(s)
#f) ; use default value #f for c
(car rest))) ; else use first optional arg
(bar a b c)))
This idiom is common in routines that perform I/O, where
a given I/O operation typically reads from or writes to a special
file--such as the standard input or output, or a log file--but
can also be used to write to other files using explicit port
objects, which are like file handles. (Ports will be
discussed in detail later.) If no port is passed to
specify where the I/O operation should be directed, it's
directed to the usual file.
Another common application of variable arity is to allow
procedures to operate on an arbitrary number of arguments.
[give example]