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
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 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.
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
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 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 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 ((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
- Weeknotes: Embeddings, more embeddings and Datasette Cloud - 17th September 2023
- Build an image search engine with llm-clip, chat with models with llm chat - 12th September 2023
- LLM now provides tools for working with embeddings - 4th September 2023
- Datasette 1.0a4 and 1.0a5, plus weeknotes - 30th August 2023
- Making Large Language Models work for you - 27th August 2023
- Datasette Cloud, Datasette 1.0a3, llm-mlc and more - 16th August 2023
- How I make annotated presentations - 6th August 2023
- Weeknotes: Plugins for LLM, sqlite-utils and Datasette - 5th August 2023
- Catching up on the weird world of LLMs - 3rd August 2023
- Run Llama 2 on your own Mac using LLM and Homebrew - 1st August 2023