Go to the first, previous, next, last section, table of contents.

Variable Arity: Procedures that Take a Variable Number of Arguments

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]


Go to the first, previous, next, last section, table of contents.