1. LISP TUTORIAL
This is a short introduction to the Common LISP programming language. This will not teach you how to write large expert systems or even simpler; after reading this you will hopefully be able to gain an understanding of what LISP is about and why it is useful and fun, and can get started with the Star Sapphire LISP product. You can get more information when you are done reading this by browsing the other help files and by playing with the system and the source examples. We also recommend reading the books mentioned in the manual's bibliography.
The concepts introduced here are defined loosely (but not inaccurately). We have found in practical experience that a rigorous approach to LISP at the beginning is the best way to alienate new users. Rather, we rely on example, induction and an attempt to convey the spirit of the language.
To get the most out of this tutorial, you should read it with LISP booted.
It has been said that there are two kinds of programming languages -- LISP and all others. This is true to a certain extent, but don't be mislead by this into thinking that by learning LISP you will be cut off from the rest of the programming world.
LISP embodies in a pure form some concepts which are present in all programming languages but which are typically obscured or difficult to use. This is for a simple reason. LISP was one of the first programming languages, and hence did not have to cater to the body of theory and preconceptions that exist nowadays. The inventor of LISP, John McCarthy, was experimenting with a notation for manipulating functions in the abstract -- a notation which, almost as an afterthought, could be implemented on digital computers. The year was 1959.
This notation is called 'lambda calculus'. Lambda calculus, developed by an English researcher named Alonzo Church during World War II, is a way of manipulating functions in the abstract, even without reference to what they actually do.
Don't let this scare you off -- you don't need a postgraduate math degree to use LISP. Even McCarthy admits that he has a difficult time with the lambda calculus. The beauty of LISP is that it has taken a very abtruse mathematical formalism and made it into a computer language which is one of the simplest in its essence. This simplicity can be grasped even by kindergarteners -- the popular LOGO language is a LISP derivative which is used everyday by children of that age group.
If LISP is different than all other programming languages, then so are the sterotypical LISP programmers. They have their own worldview and dialect: LISP hackers are the only people I know who use 'win' and 'lose' as any part of speech --sometimes in the same sentence!
But the fact that you don't have a degree from MIT or haven't spent a lot of time hanging out at the MIT AI labs shouldn't keep you out of the LISP game. This is the point of the Star Sapphire LISP product. It's time that LISP, like UNIX, move out of the hands of a small circle of gurus and made accessible to larger numbers of people.
This tutorial will introduce you to some important concepts of LISP with the intent of getting you interested in using LISP for your own purposes. First of all, let's look at how to interact with LISP in a simple fashion.
To start LISP, you simply type
lisp
from the DOS command line. After a sequence which can take between twenty seconds to a minute and a half (depending on the speed of your PC), you will see a prompt:
LISP:
This means that the LISP 'top level loop' is waiting for you to type in LISP forms.
A LISP form is an expression which embodies two kinds of information which you are trying to tell LISP: Actions that you want LISP to take, and objects for LISP to use in the course of carrying out those actions.
Possible actions could be to perform arithmetic, reverse a list of items, play the Also Sprach Zarathustra (2001) theme on your PC's speaker, or load the definition of the Colossal Cave Adventure.
Objects used in this case would be, respectively, the numbers you want to work with, the list of items to reverse, the frequencies of the notes C, G and C, and the file name advent.lsp.
Actions are performed by 'invoking' (calling) functions.
A function call is always surrounded by parentheses. The first word after the opening (left) parenthesis is the name of the function to invoke. Following this can be a sequence of LISP objects (including none at all).
For instance, to multiply 8 by itself, you can type
(* 8 8)
in response to the LISP prompt. Immediately LISP will print the answer:
64
Let's look at what's going on here.
There are actually three phases to what you just did.
First, you typed in something: An open parenthesis, the multiplication sign '*', a space, the number 8, another space, the number 8 and then a closing (right) parenthesis. LISP then read what you typed in. This is done by a function known as read. The reader converts what you typed in into an internal format which LISP can use.
Second, LISP thought about what you typed in. Without going into the specifics, LISP had to determine that what it needed to do is multiply (out of a repetoire of almost a thousand things that it knows how to do), and then go ahead and multiply the two numbers. This is called evaluation, and is done by a function called eval.
Third, LISP had to print the result. This is done by a function called print.
This is known as the read-eval-print loop, or the 'top level'. All LISP programs are run by being fed through this loop.
However, the most interesting things that LISP can do typically never show up at the top level. Most top level forms produce 'side effects'. These side effects usually involve setting up new definitions that shape the behavior of the system. LISP makes it easy to add new functions to your system which can do interesting things. LISP programmers only use the top level to either load new definitions from files, experiment with and monitor the system, or set various controls. If you only type forms to LISP at the top level, you have no easy way of permanently saving any changes you introduce into the system, or of modifying your changes later.
Let's define a LISP form.
A LISP form is a list (enclosed by parenthesis) of objects, or an object itself. An object can be an 'atom'--such as a number, a string, or a symbol; or another list.
Hence the name of LISP: it stands for LISt Processing. (A more cynical acronym is Lots of Irritating Single Parentheses).
This definition is very typical for LISP. This is what is called a 'recursive' definition. Recursion is an important part of the LISP worldview. The recursive method is a way of finding a simple way of describing something which can be arbitrarily complicated by defining it in terms of its parts.
The definition has two parts. There is a base case: The 'atom'. The atom is the simplest possible case. A recursive definition defines a thing as being one of two things: the base case, or a more complicated case which contains the original defintion.
There are many instances of recursion in nature. A tree's branches can be defined recursively as a one or more branches or leaves. In this case, a single leaf is the base case. LISP is represented internally in much the same fashion as a tree: the parentheses simply serve to represent this in a form which can be typed in. So not only is
(* 8 8)
a valid LISP form, but so is
(* 8 (+ 2 3 5))
and so is
64
All of these forms, when typed in will produce the same result. Let's look at the second form. eval treats each item in the list after the name of the function as an object which also must be evaluated. Numbers such as '64' will always evaluate to the number which you typed in. Hence in evaluating
(* 8 (+ 2 3 5))
LISP first evaluates 8 (producing 8). Then it evaluates
(+ 2 3 5)
The result of adding all these numbers is also the number 8. Both
8
and
(+ 2 3 5)
are what are called 'arguments' of the function * in the expression
(* 8 (+ 2 3 5))
Once LISP has obtained the values of all of the arguments to a function, it will run the function on its arguments. The result will be 64.
The important thing to understand here is that the functions * and + (as well as most of the other functions of LISP) evaluate ALL of their arguments, including arguments which are themselves function calls before running the given function. Understanding this behavior and its exceptions is the key to getting started in LISP.
Now, if all you want to do is simple math, you would probably be better off doing it on a pocket calculator.
LISP allows you to store values for later use in a place which can be referred to by a name called a 'symbol'. The use of symbols in LISP goes far beyond that of other programming languages. LISP is, besides a list processing language, a symbolic processing language.
When a symbol is evaluated, the value produced can be a number, a string, a list, in short any LISP object. Let's take a closer look at symbols and then return to this topic.
Most of the objects (other than parentheses) you see when you look at a page of LISP source code are symbols. LISP allows a lot of flexibility in naming symbols. If you have experienced other programming languages, you will feel a sense of freedom when dreaming up LISP symbol names. In Star Sapphire LISP, the name of a symbol can be as long as 256 letters: in other languages a symbol can only be 6, 8 or 32 letters long. So there is no excuse for using incomprehensible names for symbols in your programs!
A symbol is, loosely speaking, any sequence of letters which is not a number or other LISP object. It does not have to start with an alphabetic character as in other languages: It can start with a number, as long as some other aspect of how the letters are arranged prevent it from being interpreted as a number.
Examples of valid symbols are
x
foo
lincolns-gettysburgh-address
1-
*four-score-and-seven-years-ago*
3^+09
the-speed-of-light-squared
Note that the dash is used to separate words by convention: any other letter from a certain set could be used, for instance, the underbar, a star or a question mark:
the*speed?of*light_squared
is just as legal as the last symbol in the previous example (if a little silly looking).
Now we have stated that you can assign values to symbols.
There are several ways to assign values to symbols. The function setq is quite useful in this regard.
There is no special syntax to learn in order to use setq. It looks just like any other LISP functional form you have seen so far.
For instance,
(setq shave-and-a-hair-cut 0.25)
is the way to assign the variable shave-and-a-hair-cut the value 0.25. An interesting sidelight is that fractions (called ratios) are permitted in LISP, so writing
(setq shave-and-a-hair-cut 1/4)
is a valid assignment as well. The setq function takes any number (including zero) of variable/value pairs. (That is, there are must be an even number of arguments):
(setq x 15 y 45)
assigns the value 15 to x and then assigns the value 45 to y.
Now an interesting thing about LISP is that a symbol can take on any value, including another symbol. For instance:
(setq shave-and-a-hair-cut 'two-bits)
Will assign the value two-bits to the symbol shave-and-a-hair-cut.
Now if you type in
shave-and-a-hair-cut
LISP will reply:
TWO-BITS
Perceptive readers will have noticed something which we sluffed over above: There is a single quote in front of the symbol two-bits.
This is a quote mark. The quote mark in front of a form (any form) is an abbreviation for a function named quote The reader expands the quote mark. Typing the form
'two-bits
is read as though you typed
(quote two-bits)
(but with less typing involved). quote takes one argument. Its effect is to NOT evaluate its argument.
You should try typing in
two-bits
at the top level. If you do, you will get a message stating that the symbol two-bits is 'unbound' and a value. It is possible in LISP to have a symbol with NO value: It will be an error to try to evaluate it. This is what you did here. Now try typing
'two-bits
or
(quote two-bits)
They both do the same thing. LISP responds:
TWO-BITS
The quote form prevented the symbol two-bits from being evaluated: The result of (quote two-bits) is the symbol two-bits itself.
So that's why, in order to assign the symbolic value two-bits to the symbol shave-and-a-hair-cut we typed
(setq shave-and-a-hair-cut 'two-bits)
Now we have been typing in a good many lists: These lists have all been functional forms. However, there is no reason that a list has to have a functional meaning. The quote form allows us to do this.
For instance, you could set up a grocery list:
(setq my-shopping-list
'(asparagus handy-wipes pipe-cleaners zucchini))
and then every time you type
my-shopping-list
LISP will evaluate the symbol my-shopping-list returning the value
(ASPARAGUS HANDY-WIPES PIPE-CLEANERS ZUCCHINI)
In this case, the list is essentially meaningless to LISP except as a piece of data. Not only is there no functional definition (when the system is first started) for 'asparagus', but the arguments to 'asparagus' are all unbound (in other words, cannot be evaluated).
However, a lot of database work can be done by manipulating lists of items. A list is a good representation of various real world situations (such as going to the supermarket). Other languages force you to think exclusively in terms of numbers: Even lists must be represented using numbers (specifically pointers to memory locations), with a mechanism that some programmer must set up and maintain to hide this implementation from the user. This machinery comes free with any LISP system, and you don't have to be a world-class programmer to use it.
A good many functions are available to manipulate list data in LISP: For instance, there are named functions for each of the first ten positions in any list. If you type in
(second my-shopping-list)
LISP will return
HANDY-WIPES
and typing in
(fourth my-shopping-list)
will evaluate
ZUCCHINI
There is a built-in function to reverse lists:
(reverse my-shopping-list)
evaluates to:
(ZUCCHINI PIPE-CLEANERS HANDY-WIPES ASPARAGUS)
Note that this does not actually reverse the list itself, it returns a reversed copy of the list. The next time you use
my-shopping-list
you will still get the value:
(ASPARAGUS HANDY-WIPES PIPE-CLEANERS ZUCCHINI)
Three oddly named functions are considered the most essential of these list functions: cons, car, and cdr. To understand what these functions do (and how they got these peculiar names) we will have to understand a little about how lists are represented internally in LISP.
The object that holds lists together is called a 'cons'. This stands for a list CONStructor. A cons is an actual memory location, which contains two pointers. A pointer is a reference to another memory location, which can be a cons, or a number, a symbol or some other object.
There is a special memory location which means 'nowhere'. This can be written using a symbol named nil (which is Latin for 'nothing'). The tail of every list is nil-terminated: otherwise the print function would fall off the end of the list (among other disasters).
The cons function constructs a raw list object of its two arguments. For instance, if you type
(cons 'zucchini nil)
you will get the result
(zucchini)
This is a properly nil-terminated list with one element. It's important to understand that a list can be terminated with anything, as long as it is not a list. If you typed
(cons 'asparagus 'zucchini)
LISP prints a funny looking result:
(ASPARAGUS . ZUCCHINI)
This is called a dotted list. The tail element of this cons is not nil it is a symbol, ZUCCHINI. The list consisting solely of ASPARAGUS could be typed in as
'(asparagus . nil)
(the space surrounding the dot is important) but LISP will always print this simply as
(ASPARAGUS)
The dotted list notation
(head . tail)
can be thought of as equivalent to
(cons head tail)
in somewhat the same way that 'foo is the same as (quote foo).
To compose a list the hard way, we can do cons once for each element in the list:
(cons 'asparagus
(cons 'handy-wipes
(cons 'pipe-cleaners
(cons 'zucchini nil))))
will return the same thing as typing in
'(asparagus handy-wipes pipe-cleaners zucchini)
or
'(asparagus handy-wipes pipe-cleaners zucchini . nil)
Note that nil does not have to be quoted: Its constant value is nil itself.
This phenomena is called 'consing-up' and is used in LISP slang to refer to the building anything piece by piece, for instance:
"let's cons up a volleyball team"
even if not refering specifically to conses.
To take a cons apart you can use the car and cdr functions. These were originally named after certain registers in the computer that was first used to implement LISP, and the names have stuck. The car and cdr can be thought of as the 'head' and 'tail' of a list. The car is the head element in the car:
(car my-grocery-list)
is just
ASPARAGUS
and the cdr is the tail:
(cdr my-grocery-list)
will evaluate to
(HANDY-WIPES PIPE-CLEANERS ZUCCHINI)
So the net effect of
(setq my-grocery-list
(cons (car my-grocery-list)(cdr my-grocery-list))
is to take my-grocery-list apart and put it back together.
So far we have been typing in forms at the top level which have no side effects (other than setq).
You can modify LISP's repertoire of functions by writing new functions using a function named defun.
Although your function can be as complicated as you want, and defun allows you to use many sophisticated options not available in other programming languages, all defuns are composed of just three parts: A name, a parameter list, and a body.
The name is a symbol which, when found at the head of a list, will be used to invoke your function.
The parameter list is a list of variables which specify how many arguments the function will accept and names by which they can be referred to in the body of the function. The body of the function is a sequence of LISP forms which will be evaluated every time that the function is invoked. The defun will return the value of the last form: You can also use the return and return-from forms in the body of a defun to return values at any time.
Here is a simple function definition using defun.
(defun cube(x)
(* x x x))
The name of the function is 'cube'. The parameter list (x) specifies that your function will accept one argument. This can be referred to as 'x' inside the body of the function -- however, outside the body of the defun x will be unbound unless you have otherwise set some value for it. The body of the defun consists of the expression x: So the function will return what this returns, in otherwords the cube of x (x multiplied by itself three times).
You can then invoke your function as
(cube 3)
for instance, which will evaluate to
81
Note that if you give too many arguments, or too few arguments, LISP will say so.
Well, as you can see, LISP is not too difficult to get your feet wet in -- however, LISP is the Pacific Ocean of computer languages. We hope that you have as much fun exploring this product as we did implementing it.
- John Bruno Hare