Common Lisp/Basic topics/Functions
A function is a concept that is encountered in almost every programming language, but in Lisp functions are especially important. Historically, Lisp was inspired by lambda calculus, where every object is a function. On the other end of the spectrum there are many programming languages where functions are hardly objects at all. This is not the case with Lisp: functions here have the same privileges as the other objects, and we will discuss it in this chapter.
Defining functions
[edit | edit source]Functions are most often created using the defun (DEfine FUNction) macro. This macro takes a list of arguments and a sequence of Lisp forms, called a body of a function. A typical use of defun is like this:
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(+ x1 x2))
Here, the list of arguments is (x1 x2) and the body is (print x1) (print x2) (+ x1 x2). When the function is called, each form in the body is evaluated sequentially (from first to last). What if something happened and we want to return from the function without reaching the last form? The return-from macro allows us to do that. For example:
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(unless (and (numberp x1) (numberp x2))
(return-from print-arguments-and-return-sum "Error!"))
(+ x1 x2))
The second argument to return-from is optional, which means that it can be called with only one argument - in this case the function would return nil. But wait: how did they do this? Our function accepts exactly two arguments: nothing more, nothing less. The answer is that what we called "argument list" is not as simple as it seems. It is in fact called lambda list and it is not the only place where we will encounter it. Later, I will explain how to allow optional and keyword arguments in functions.
Functions as data
[edit | edit source]As was mentioned in the beginning of this chapter, Lisp functions can be used like any other object: they can be stored in variables, passed as parameters to other functions, and returned as values from functions. In the previous section we defined a function. The function in now stored in the function cell of the symbol print-arguments-and-return-sum - defun put it there. However, it is not bound to this location forever - we can extract it and put in some other symbol, for example. To access the function cell of a symbol we can use the accessor symbol-function. Let's store our function in some other symbol:
(setf paars (symbol-function 'print-arguments-and-return-sum))
The first reaction would be to do something like that:
>(paars 1 1)
EVAL: undefined function PAARS
[Condition of type SYSTEM::SIMPLE-UNDEFINED-FUNCTION]
This is because we put the function into the value cell of the symbol instead of its function cell. It's easy to fix:
(setf (symbol-function 'paars)
(symbol-function 'print-arguments-and-return-sum))
Since symbol-function is an accessor we can use setf with it. Now (paars 1 1)
produces what it should.
While symbol-function is there for a reason, it is almost never used in the real code. This is because it is superseded by several other Lisp features. One of them is the function special operator. It works like the symbol-function special operator, except it doesn't evaluate its argument, and it returns the function that is currently bound to the symbol, which may not actually be its function cell. (function foo)
may also be abbreviated as #'
which tremendously improves its usefulness. On the flip side, it's impossible to write
(setf (function paars) (function print-arguments-and-return-sum))
Fortunately it's possible to call functions from other places than function cell of a symbol. A function designator is either a symbol (in this case its function cell is used) or the function itself. funcall and apply are used to call functions by their function designators. Remember that symbol paars now contains the same function in its function cell and its value cell. Let's change its value cell so that the difference is apparent:
(setf paars #'+)
Now let's funcall it in different ways:
(funcall paars 1 2) ;equivalent to (+ 1 2)
(funcall 'paars 1 2) ;equivalent to (funcall (symbol-function paars) 1 2)
(funcall #'paars 1 2) ;equivalent to (paars 1 2)
The difference between the second and third example is that if paars was temporarily bound (with flet or labels) to some other function, the third funcall would use this temporary function, while the second funcall would still use its function cell.