- Functions in Lisp are created with the
*macro*defundefun name lambda-list [[ {declaration}* | doc-string ]] {form}* what means: (defun < function-name > (< arg-1 > < arg-2 > ... < arg-n >) "documentation string" < lisp-forms > )

- The first argument to
*defun*is a symbol (defun is a*special form*, so its first argument will not be evaluated) to which the definition of the function should be bound; this is a fancy but precise way of saying that the first argument is the name of the function being defined. This means that defun has the effect of creating a new function and the side-effect of binding that new function to the symbol*name*(as we will see later, symbols can have both values and functions bound to them simultaneously). We say that defun is called*for effect*because we are more interested in its side effect of establishing the definition of a new function rather than the value it returns. - The second argument to defun is a list (that will
*not*be evaluated) of the function's parameters. In formal logic and programming language theory this is called a*lambda list*, a term that comes from Alonzo Church's Lambda Calculus. - The third are two optional arguments, the first one being a
*declaration*(we will not talk about this by the moment) and the second one a*documentation string,*a string of characters that can be accessed with the function documentation (we will see an example below). This is a easy and convenient way to document your new function, giving an idea of what it does in the form of a text. - The fourth and any additional arguments are the Lisp forms whose evaluation represent the
*body*of the function, i.e. the program itself. - Lets create a very simple function:
(defun harmonic-series (frequency n) ;;; Documentation String "prints the harmonic-series with fundamental < frequency > and < n > harmonics" ;;; Body ;;; First Form (loop for j from 1 to n do (print (* frequency j))) ;;; Second Form 'done)

- Note that once a call to
*defun*is evaluated, the new function (in this case*harmonic-series*) becomes part of the current Lisp environment and can be invoked freely. - When the function
*harmonic-series*is invoked, the values of the actual parameters in the function call are*temporarily*bound to the functions formal parameters:(harmonic-series 440 5.0) \_/ \_/ | n frequency

- Then each of the forms of the body of the function are evaluated
*in order*and the value of the last form is returned as the value of the function (the last evaluation is the form*'done*in our function, as we see*harmonic-series*is called*for effect*as it was defun). - We can ask for documentation about our new function:
(documentation 'harmonic-series 'function)

- Our
*harmonic-series*needs 2 parameters to work: < frequency > and < n >. This function can only compute harmonic series because is multiplying the fundamental*frequency*by integer numbers. We can add an**&optional**parameter*ratio*to our function, it will be initialize to 1 (the default value) and can be changed just by adding a third argument while calling the function:(defun harmonic-series (frequency n &optional (ratio 1)) "prints the harmonic-series with fundamental < frequency > and < n > harmonics at a ratio of < ratio >" (loop for j from 1 to n and for r from 1 by ratio do (print (* frequency r))) 'done)

- Harmonic-series has the same functionality as before if we call it without the optional parameter:
(harmonic-series 440 5)

- And its new functionality if we call it with an optional value different to 1:
(harmonic-series 440 5 (expt 2 1/2))

- Lets expand the functionality of our function even more. We can add another
**&optional**argument*sel*that will let us print the*odd*or*even*elements of our series:(defun harmonic-series (frequency n &optional (ratio 1)(sel nil)) "prints the harmonic-series with fundamental < frequency > and < n > harmonics at an optional ratio of < ratio >. The < sel > optional must be 'odd or 'even" (if sel (if (equal sel 'even) (loop for j from 1 to n and for r from 2 by (* 2 ratio) do (print (* frequency r))) (if (equal sel 'odd) (loop for j from 1 to n and for r from 1 by (* 2 ratio) do (print (* frequency r))) (error "sel must be 'odd or 'even"))) (loop for j from 1 to n and for r from 1 by ratio do (print (* frequency r)))))

- We can call our function using its new feature:
(harmonic-series 440 5 1 'odd) (harmonic-series 440 5 1 'even) (harmonic-series 440 5 1 'all)

- If we want to add other
**&optional**parameters to our function, the calls to it will start to be confusing, and we can't change one of the last parameters only leaving the rest of them with their default values. We can use**&key**parameters to avoid this problem:(defun harmonic-series (frequency n &key (ratio 1)(sel nil)(start 1)) "returns a list of the harmonic-series with fundamental < frequency > and < n > harmonics. Key arguments: < ratio > < sel > (must be 'odd or 'even), < start >" (let ((output-list nil)) (if sel (if (equal sel 'even) (loop for j from 1 to n and for r from (+ start 1) by (* 2 ratio) do (push (* frequency r) output-list)) (if (equal sel 'odd) (loop for j from 1 to n and for r from start by (* 2 ratio) do (push (* frequency r) output-list)) (error "sel must be 'odd or 'even"))) (loop for j from 1 to n and for r from start by ratio do (setf output-list (push (* frequency r) output-list)))) (reverse output-list)))

- Note that we have transformed our function from a
*for effect*called function to a*for value*called function, i.e. now we are interested in the returned value instead of the side effect of printing. - Other lambda-list keyword that we can use is
**&aux**. For example*output-list*is just used in*harmonic-series*as the container to give the output in the format of a list, instead of using a let to define it inside the function's body, we can pass the function the empty list as an auxiliary parameter. Normally**&aux**arguments are not*user*parameters. So the function can be written this way:(defun harmonic-series (frequency n &key (ratio 1)(sel nil)(start 1) &aux (output-list nil)) "returns a list of the harmonic-series with fundamental < frequency > and < n > harmonics. Key arguments: < ratio > < sel > (must be 'odd or 'even), < start >" (if sel (if (equal sel 'even) (loop for j from 1 to n and for r from (+ start 1) by (* 2 ratio) do (push (* frequency r) output-list)) (if (equal sel 'odd) (loop for j from 1 to n and for r from start by (* 2 ratio) do (push (* frequency r) output-list)) (error "sel must be 'odd or 'even"))) (loop for j from 1 to n and for r from start by ratio do (setf output-list (push (* frequency r) output-list)))) (reverse output-list)) some calls to the function: (harmonic-series 440 5 :ratio 1 :start 15) (harmonic-series 440 5 :ratio 1 :start 15 :sel 'odd) (harmonic-series 440 5 :ratio (expt 2 1/2) :sel 'even) (harmonic-series 440 5 :start 10)

- Our
*harmonic-series*function has a fixed number of parameters, some of them are mandatory (frequency and n) and some of them are optional keys (ratio, sel, start). Sometimes we need functions that can get an arbitrary number of parameters, this is the case of +:(+ 1 2) (+ 1 2 3 4 5) (+ 1 2 3 4 5 6 7 8 9 10 11)

- Imagine you want to create a function that will take a series of note arguments of an arbitrary length and return you a list with the frequencies corresponding to those pitches. We can do this using the
**&rest**keyword.(defun pitch-list (&rest note-list) "returns a list with the frequencies of the notes passed as arguments" (let ((out-list nil)) (dolist (i note-list) (push (pitch i) out-list)) (reverse out-list)))

- All the arguments passed to the function after the
**&rest**flag are encapsulated in a list that is bound to the local variable*note-list*. Here are some calls to our new function:(pitch-list 'c4 'e4 'g4 'c5) (pitch-list 'c4 'ef4 'g4) (pitch-list 'GS8 'AS8 'C9 'D9 'DS9 'F9 'FS9 'G9 'GS9 'AS9)

- Suppose we want to add another functionallity to our harmonic-series, to give you the possibility of having the name of the notes corresponding to the partials of the series instead of their frequency. We can add another &key paraneter called
*notes*with value of NIL, if we pass this argument as T (true) the functionwill the notes:(defun harmonic-series (frequency n &key (ratio 1)(sel nil)(start 1)(notes nil) &aux (output-list nil)) "returns a list of the harmonic-series with fundamental < frequency > and < n > harmonics. Key arguments: < ratio > < sel > (must be 'odd or 'even), < start >" (if sel (if (equal sel 'even) (loop for j from 1 to n and for r from (+ start 1) by (* 2 ratio) do (push (* frequency r) output-list)) (if (equal sel 'odd) (loop for j from 1 to n and for r from start by (* 2 ratio) do (push (* frequency r) output-list)) (error "sel must be 'odd or 'even"))) (loop for j from 1 to n and for r from start by ratio do (setf output-list (push (* frequency r) output-list)))) (if notes (reverse (mapcar #'note (mapcar #'float output-list))) (reverse output-list)))

- The last line of the form introduces the function mapcar, that takes a function and a list as arguments. The
**#'**character is used to*quote*a function name, a special type of quote. Mapcar applies the function passed as first argument to all the elements in the list passed as second argument:(mapcar #'float '(1 2 3))

- A call to the new version of harmonic-series:
(harmonic-series 440 5 :notes t)

- The function
*note*must get floating point numbers as arguments, that is why we must map the function*float*through the out-list before mapping the function*note*. We can collapse the two calls of*mapcar*into one using a local*anonymous lambda function*:(defun harmonic-series (frequency n &key (ratio 1)(sel nil)(start 1)(notes nil) &aux (output-list nil)) "returns a list of the harmonic-series with fundamental < frequency > and < n > harmonics. Key arguments: < ratio > < sel > (must be 'odd or 'even), < start >, < notes >" (if sel (if (equal sel 'even) (loop for j from 1 to n and for r from (+ start 1) by (* 2 ratio) do (push (* frequency r) output-list)) (if (equal sel 'odd) (loop for j from 1 to n and for r from start by (* 2 ratio) do (push (* frequency r) output-list)) (error "sel must be 'odd or 'even"))) (loop for j from 1 to n and for r from start by ratio do (setf output-list (push (* frequency r) output-list)))) (if notes (reverse (mapcar #'(lambda (x) (note (float x))) output-list)) (reverse output-list)))

- The function
**lambda**is used to define a*local*function,**lambda**operates the same as*defun*but instead of binding the function definition to a symbol it just creates the function and returns the new function as its value. This kind of function is called*anonymous*function. This is a very useful way of creating functions on the fly inside of other functions. Here is a call to the new version of harmonic-series:(harmonic-series 440 5 :notes t)

- harmonic-series is a simple example showing the programming flexibility of Lisp.

Back to LispWorkshop main page.

©1996-98 by Juan Pampin,

*juan@ccrma.stanford.edu*