CCRMA

Understanding arguments in Common Lisp

by Matt Wright, Jan 8, 2004


Some help in understanding how arguments work in Common Lisp

What are arguments?

Lisp programming is all about defining functions. Functions are general-purpose chunks of a program, and the arguments to a function are the specific data that you ask the function to deal with when you call the function. (CLM's instruments that you define with definstrument work exactly like regular Lisp functions in terms of arguments.)

For example, rather than finding the frequency in Hertz of a particular note like middle C, it's more useful to have a function that can find the frequency in Hertz of any note. Common Music's hertz function does exactly this:
CM(55): (hertz 'c4)
261.62555
CM(58): CM(58): (hertz 'd4)
293.66476
CM(61): CM(61): (hertz 'e10)
21096.164

We say that hertz takes one argument, and in the examples above we called hertz three times, first with the symbol c4, then with the symbol d4, then with the symbol e10.

The point is that hertz is useful because it doesn't have any particular note built into it, but instead it can give the frequency of any note that you might ask about, i.e., any note name that you might pass as an argument to the function.

The simplest case: fixed number of arguments

The easiest kind of function to use is one that takes a fixed number of arguments. For example, we've seen the built-in function listp that tells whether or not its argument is a list. This function takes exactly one argument, so if you call it with one argument it will tell you whether that argument is a list, and if you call it with more or fewer arguments, it will cause an error:
CM(11): (listp '(1 2 3))
T
CM(16): (listp 'hello)
NIL
CM(19): (listp 1 2 3)
Error: LISTP got 3 args, wanted 1 arg.
  [condition type: PROGRAM-ERROR]
[1] CM(22): :reset
CM(23): (listp)
Error: LISTP got 0 args, wanted 1 arg.
  [condition type: PROGRAM-ERROR]
[1] CM(24): :reset

User-defined functions usually take a fixed number of arguments, namely, the number of elements in the argument list of the defun that defines the function. For example, this procedure implements the quadratic formula for finding the roots of a 2nd-degree polynomial:
(defun quadratic-formula (a b c)
  (list (/ (+ (- b) (sqrt (- (* b b) (* 4 a c))))
	   (* 2 a))
	(/ (- (- b) (sqrt (- (* b b) (* 4 a c))))
	   (* 2 a))))

Don't worry about the mathematical details; the point is that this function is defined to take exactly three arguments, because after the name of the function (which is quadratic-formula), there is the three-element list (a b c). We say that a, b, and c, are the formal parameters of the quadratic-formula procedure. Since there are three formal parameters, every time you call quadratic-formula you have to give it three arguments or you'll get an error:
CM(46): (quadratic-formula 1 -2 1)
(1.0 1.0)
CM(47): (quadratic-formula 1 2 -3)
(1.0 -3.0)
CM(48): (quadratic-formula 1)
Error: QUADRATIC-FORMULA got 1 arg, wanted 3 args.
  [condition type: PROGRAM-ERROR]
[1] CM(51): :reset
CM(52): (quadratic-formula 1 2 3 4)
Error: QUADRATIC-FORMULA got 4 args, wanted 3 args.
  [condition type: PROGRAM-ERROR]

Restart actions (select using :continue):
 0: Ignore the extra args.
[1c] CM(54): :reset

Functions that can take an arbitrary number of arguments

Sometimes it's useful for a function to be able to take any number of arguments. For example, Common Lisp's built-in + function will find the sum of any number of numeric arguments:
CM(68): (+ 1 2)
3
CM(71): (+ 1 2 3)
6
CM(74): (+ 1 2 3 4 5 6 7 8 9 10)
55
CM(77): (+ 6)
6
CM(78): (+)
0

If you want to write your own function to take any number of arguments, you use the special keyword &rest. In your definition you first list all the normal, required parameters, then, after &rest, you give the name of a single parameter that will hold a list of all the remaining arguments. (In other words, all the "rest" of the arguments, hence the name.) Here's an example:

(defun silly (first &rest others)
  (list 'you 'gave first 'followed 'by (length others) 'more 'arguments))

SILLY
CM(100): (silly 1 2 3 4 5)
(YOU GAVE 1 FOLLOWED BY 4 MORE ARGUMENTS)
CM(103): (silly 'fernando 'is 'a 'good 'lisp 'programmer)
(YOU GAVE FERNANDO FOLLOWED BY 5 MORE ARGUMENTS)
CM(106): (silly)
Error: SILLY got 0 args, wanted at least 1 arg.
  [condition type: PROGRAM-ERROR]

Of course you don't have to have any normal arguments before &rest; all the arguments can be "rest" arguments:
CM(110): (defun count-arguments (&rest args) (length args))

CM(117): (count-arguments 1 2 3 4 5)
5
CM(120): (count-arguments)
0

When you look up Common Lisp's built-in functions in Steele, you'll see this same &rest syntax to tell you what arguments each function takes. The + and - functions are good examples; + takes any number of arguments, while - takes one or more arguments.

Keyword Parameters

In all the examples we've seen so far, we keep multiple arguments straight by the order in which they appear. For example, when we defined quadratic-formula, we said that the three formal parameters were a, b, and c, in that order, because of the list (a b c) in the function definition. When we later called quadratic-formula with the three particular numbers 1, 2, and -3, Common Lisp knew that 1 was the value for a, 2 was the value for b, and -3 was the value for c, just because of the order that they appeared in the expression (quadratic-formula 1 2 -3).

This method for associating arguments with formal parameters can get out of hand when there are lots and lots of arguments. For example, the FM violin instrument we've been using (defined in the file v.ins) has 38 arguments. It would be terrible to have to type in 38 values every time you wanted to play a note on this instrument, not to mention trying to remember the order of the 38 arguments.

Common Lisp's Keyword Parameters have two features that help a lot in these situations:

Here's an example:

(defun poem (&key (rose-color 'red) (violet-color 'blue))
  (list 'roses 'are rose-color 'and 'violets 'are violet-color))

CM(145): (poem)
(ROSES ARE RED AND VIOLETS ARE BLUE)
CM(146): (poem :rose-color 'pink)
(ROSES ARE PINK AND VIOLETS ARE BLUE)
CM(149): (poem :violet-color 'violet :rose-color 'yellow)
(ROSES ARE YELLOW AND VIOLETS ARE VIOLET)
CM(152): (poem :rose-color 'white :violet-color 'purple)
(ROSES ARE WHITE AND VIOLETS ARE PURPLE)

First, you can see the syntax for defining a procedure with keyword arguments: the special symbol &key followed by a series of two-element lists each containing a keyword parameter name followed by a default value. In this example there are two keyword parameters, rose-color and violet-color, whose respective default values are the symbols red and blue.

In the first invocation of poem there are no arguments, so both keyword arguments get their default value. In the later invocations we provide values for one or both keyword arguments; note that you must use a colon before the name of the keyword argument. Note also that the keyword arguments can be given in any order.

Mixing normal and keyword arguments

The same function can have both normal and keyword arguments:
(defun poem2 (punchline &key (rose-color 'red) (violet-color 'blue))
  (append (list 'roses 'are rose-color 'and 'violets 'are violet-color)
          punchline))

CM(145): (poem2 '(im schizophrenic and so am i))
(ROSES ARE RED AND VIOLETS ARE BLUE IM SCHIZOPHRENIC AND SO AM I)
CM(146): (poem2 '(ccrma director chris chafe plays the cello) :violet-color 'yellow)
(ROSES ARE RED AND VIOLETS ARE YELLOW CCRMA DIRECTOR CHRIS CHAFE PLAYS THE CELLO)

Calling CLM instruments with tons of keyword arguments

All of the CLM instruments that will be provided in this class have reasonable default values for all keyword arguments, so in general it's safe to just ignore the keyword arguments, provide values for the required arguments, and assume that you'll get a useful result. As you want more control over the instrument, you'll learn about its keyword arguments.

One important clue for finding out what kind of value should be passed for a given keyword argument is to look at the default value. If the default value is 0.5, there's a good chance that you should pass a floating point value to this parameter; if the default is '(0 1 1 .5 0 1.), then you should probably pass a list, and in this case, likely a list in the envelope-specifying format. You get the idea.

The gory details

The section on Lambda-Expressions in Steele gives you the complete story on all this good stuff.

To understand it, you'll need to know that lambda is the function that creates functions. Good luck.

©2004 Matt Wright. All Rights Reserved.
matt@ccrma.stanford.edu