Lisp special forms
3rd October 2002
Lisp Special Forms
Special forms are generally exceptions to normal Lisp syntax that make coding easier and more convenient for the programmer. They are expressions that do not follow the normal rules of evaluation. Examples we have met so far include quote
and if
.
setq
setq is used to set variables in Lisp. (setq x 7)
is the Lisp equivalent of x = 7;
in other languages. Variables are used far less in Lisp than they are in C and Java, and a program that uses setq
a lot is likely to have structural problems. The reason for this is that in Lisp every expression returns something, so variables are far less important. Don’t try to write C in Lisp—think about things in a different way.
> (setq x 7) 7 > x 7 >
Some older versions of Lisp include a function called set
, where (set x 7) first evaluates x, then assigns 7 to whatever was returned by x. This can be highly confusing which is why setq
(which automatically quotes the variable for you) is preferred.
Variables in Lisp do not
have types—you can assign anything to a variable be it a number, string or list without having to worry about the type of the variable. In Lisp the data has the type; in FC the variable has the type and the data is just a bunch of bits which are interpreted in different ways depending on the type of the variable they are stored in.
defun
defun is the special form used to define a function:
(defun f (n) (print n) (+ n 1))
In the above example, f
is the function name and (n)
is the list of arguments taken by the function. The main body of the defun statement contains the statements that should be executed. The function returns the value of the last executed statement in the function body.
> (f 7) 7 8 > (defun sc (x) ; returns sin(x) if x>0, cos(x) otherwise (if (> x 0) ; Note that Lisp comments start with a ; (sin x) ; and continue to the end of the line (cos x))) Alternatively: (defun sc (x) ((if (> x 0) sin cos) x))
The second example above has the same effect as the first example but works in a different way—instead of applying the function selectivelyan if statement is used to return the required function before it is applied to x. This is a neat demonstration of Lisp’s powerful ability to return functions from statements and apply them to data.
Just as variables are untyped, so are functions. A function can return any value.
lambda
While defun
is very useful, it is not the most powerful way of creating a function. lambda is a special form which can be used to return an anonymous function—a function with no name.
> (lambda (n) (+ n 1)) #<Procedure #f2e18> > ((lambda (n) (+ n 1)) 5) 6 >
The existence of lambda makes defun theoretically redundant. The following expressions are semantically equivalent:
(setq f (lambda (n) (+ n 1))) (defun f (n) (+ n 1))
In practise however, defun
has several benefits due to performance enhancement in the way defun is implemented.
A good practical use of lambda is as a way of creating functions on demand. Consider the following code:
> (defun f (n) (lambda (m) (+ m n))) f > (setq g (f 3)) #<Procedure #ebef8> > (g 4) 7 >
Here function f is acting as a kind of function factory—it is used to create function g, which adds 3 to its arguments. Function f could be called repeatedly to create functions for adding different values. Note that once the above code has been executed g will always add on 3, no matter now often the f function is used to create new functions. This is called closure, and will be discussed in more depth further on in the course.
when and unless
when and unless are two more special forms which are both essentially variants of if
. Both take any number of expressions as arguments and will evaluate ALL of the expressions (or not) depending on the outcome of the first expression:
(when expr1 expr2 expr3) ; Will exeute expr2 and expr 3 is expr1 evaluates to true (unless expr1 expr2 expr3) ; Will exeute expr2 and expr 3 is expr1 evaluates to false
So unless
is equivalent to “when not”. Both of these special forms are syntactic sugar—they are there purely for the convenience of the programmer.
progn
progn is a special form that stands for “program with no arguments”. It takes a number of expressions and groups them all together in to a single expression that returns the value of the last expression in the group. This is useful for writing if statements that execute more than one expression at a time.
> (progn (print "hi there") (print "bye")) hi there bye "bye" ; The return value of the overal expression >
progn
takes its name from prog
, which is a feature that existed in older Lisp versions. prog
was a program with variables. This takes us neatly on to our next topic—let
and local variables.
let
let is a special form used to introduce local variables. A local variable exists only for the duration of its scope, which in Lisp is the length of the let
statement.
(let ((x 1) (y 2)) (print x) (print y))
Statements within the body of the let
statement can use local variables assigned in the list at the start of the let segment. Once we exit the let statement the symbols used as local variables return to the state they were in before the let statement was executed.
There is an important exception:
(let ((x 1) (y x)) ... )
In the above code the order of evaluation must be taken in to account. The variables on the right of the local variable setting pairs are evaluated first, before the assignments are made. Here, 1 will be assigned to x but the previous value of x (before the let statement began) will be assigned to y. This behaviour can be avoided using let*
which is less efficient than let but does not suffer from the feature described above.
Note that let can be emulated using lambda—the above statement is equivalent to the following:
((lambda (x y) .... ) 1 x)
This also demonstrates that lambda
itself uses local variables—the values of x and y before the lambda statement are left unchanged afterwards.
Lisp functions in real code tend to look something like this:
(defun f (a b) (let (...) ... ))
So functions tend to create their own local variables and use those, rather than risk modifying global variables in unexpected ways.
EuLisp and Classes
EuLisp (and many other modern Lisp implementations) includes an extensive class structure. The class-of
function can be used to find the class of any value or expression. Classes are objects in their own right, so classes can themselves have classes. A function called make
can be used to create instances of classes, in conjunction with class keywords which are symbols of the form something:
. For example, another way of creating a vector is to use (make <simple-vector> size: 9)
.
More recent articles
- Storing times for human events - 27th November 2024
- Ask questions of SQLite databases and CSV/JSON files in your terminal - 25th November 2024
- Weeknotes: asynchronous LLMs, synchronous embeddings, and I kind of started a podcast - 22nd November 2024