- Functions in Lisp are created with the macro defun
defun 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