LISP

Many thanks to Adam Pingel for access to previous Lisp notes.

Getting started

The version of LISP that we will be using in class is gcl, which is available on the SEAS machines.

Here's a sample gcl session:

Related Links

Editors/Interpreters

Other Links


Quick Reference

The following is a quick guide to lisp, even shorter than the above "primer." It is recommended that if you want to fully understand lisp you go through one of the guides, but this section may be useful for a quick check for a certain operator. Let me know if there is anything else you want added to this section and I will do my best to put it up here.

Prefix notation and math operators

LISP expressions are written using prefix notation. Here are some example mathematical expressions using +, -, *, and /. Also note that these expressions can be nested.

Non-numeric atoms

In addition to numbers, we can also refer to non-numeric literals by prepending the symbol with a single quote character. This tells the LISP interpreter not to evaluate the symbol. Note that these are case-insensitive.

Lists

The list is the fundamental building block of LISP expressions. As with the symbols in the previous point, lists must be prepended with a single quote character to prevent the interpreter from evaluating them.

Nil, the empty list

There is a special symbol called NIL that is a synonym for the empty list. As we shall see in the next point, it is also interpreted as the Boolean false.

Boolean expressions

LISP uses NIL to mean Boolean false. Any other value is considered to be true, though LISP uses a particular value, T, to represent true. The functions and, or, and not have the standard meaning.

Equality

= is used to compare numerical values. equal is used to compare complex expressions. gcl's versions of these operators can take more than two arguments. This is not true of all lisps.

if and cond control structures

if requires three arguments: 1) the condition, 2) the true clause, and 3) the false clause.

cond takes a variable number of arguments. Each argument has two parts, a condition and the action to take if that condition is true. Evaluation of a cond expression iterates through argument until an argument with a true condition is found. When writing a cond expression, make sure that at least one of the conditions will be true for any argument. A common way to ensure this is to end the list of cond arguments with an expression whose condition is t. This is illustrated below.

Checking for the empty list: the "null" predicate

There is a built-in predicate for checking whether a list is nil called null.

atom and listp predicates

Often times when writing a recursive function (as well as many other situations) it is useful to know whether a variable is a list or not. The predicates listp and atom do this.

Taking lists apart: car and cdr

To refer to sub-parts of a list, use car or cdr. car returns the element at the head of the list. cdr returns the list that is everything except the head of the list. first is a synonym for car, and rest is a synonym for cdr.

Constructing lists: cons, list, and append

The three ways of creating lists are cons, list, and append. Their behaviors are best illustrated with examples.

Variables, scope, and let

Variables are created with let statements. The variables created are scoped to the let statement, and can be masked if an enclosed let expression redefines the variable.

Defining functions: defun

New functions are defined with the defun operator. The variables are listed in parenthesis after the function name.

Example of recursion on numbers: fibonacci

Example of recursion on lists: reverse

Example of variable binding

comments

The semicolon (;) is LISP's comment character.

load

From an interactive gcl session, you can do (load "hw1.lsp") to interpret the specified lisp file. However, we will be excuting your code with the following command line:

io

To get input from a user, you can print a prompt to *query-io* using the format operator, and then do a read-line on *query-io*. Let's demonstrate this by putting the following code in a file called "askquestion.lsp": If we run the code from the shell like so, we will see something like this. The bold characters are the ones I typed. The format operator takes format strings that may remind you of C's printf. One useful and non-obvious format string is demonstrated below:

There are a couple of handy predicates that get yes/no input from the user:

symbols vs. strings vs. characters

The string operator is used to convert a symbol into a string. To convert from a string into a symbol, see the discussion here. Also check out the string-equal operator (demonstrated below). Rob Adams found these goodies on the CL Cookbook (see link above):

Unfortunately, these don't work on the ancient version of gcl installed on the SEAS machines. Rob supplied a version of "explode" that is compatable with the version of gcl we will be using:

eval

eval does the obvious thing to a lisp expression. This is possible because lisp expressiona are really just lists like every other complex data structure.

lambda, closures, apply, funcall, and mapcar

I'll talk about these more in discussion section. One thing to note here is that the word "thunk" is often used as a synonym for "closure".

A closure is defined as a double whose two elements are a pointer to code and a pointer to an environment. In the adder example below, the reason we need the "environment pointer" is that we need a way to remember what n was bound to at the time that (adder 3) was created.

This particular "adder" example comes from the CL Cookbook.

Given these definitions, we can define our own mapcar by recursively applying funcall.

Finally, note the special #' symbol that can replace (function ...). The adder example above could be rewritten as:

There are two slightly different ways of using mapcar. One is by passing it a lambda block, which is created by simply prepending the #' in front of the function name. The other is to pass it a closure:

compose

Let's take a look at a "higher-order" function, which means that it takes functions as arguments. The compose function is the same as in mathematics. f(g(x)) = compose(f,g)(x)

curry and uncurry