Title: An Introduction to LISP
1An Introduction to LISP
- LISP is a functional language that was invented
by John McCarthy, an AI pioneer, in 1959 - LISP stands for LISt Processing
- Lists are the primary data structure in LISP
- The variant of LISP used most today is called
Common LISP - Scheme is directly descended from LISP
- LISP is a functional programming language
- Recall that Prolog is a logic programming
language - C is both imperative and object oriented
- Imperative features have been added to LISP and
Prolog for practical purposes - It would take a whole quarter to thoroughly learn
LISP - Our goal is to learn enough to see how LISP
facilitates building AI systems
2Symbolic Expressions
- Everything you write in LISP is a symbolic
expression (S-expression) -
S-expression - Atom
List - Number Symbol
- Five example atoms are 32, , john, r2d2, 5.125
- Two example lists are (32 john r2d2
5.125) -
(happy students love CS480/580)
3Lists and S-Expressions
- Lists represent both data and programs
- Lists can be nested to any depth
- Example lists can represent predicate calculus
statements - (likes joe chocolate)
- (not (likes fred broccoli))
- Example lists can represent arithmetic
expressions - ( 2 3)
- ( ( 2 3) ( 1 2))
- A LISP interpreter will try to evaluate any
S-expression entered - An S-expression which can be evaluated is called
a form - Example gt ( 2 3)
- 5
- gt ( ( 2 3) ( 1 2))
- 15
4Formal Definition of S-Expression (from Luger)
- An s-expression is defined recursively
- An atom is an s-expression.
- If s1, s2, , sn are s-expressions, then so is
the list (s1 s2 sn). - A list is a nonatomic s-expression.
- A form is an s-expression that is intended to be
evaluated. If it is a list, the - first element is treated as the function name and
the remaining elements are - evaluated to find the function arguments.
- To evaluate an s-expression
- If the s-expression is a number, return the value
of the number. - If the s-expression is an atomic symbol, return
the value bound to that symbol if the symbol is
not bound, it is an error. - If the s-expression is a list, evaluate the
second through the last arguments and apply the
function indicated by the first argument to the
results.
5Working with Lists
- There is one special value, nil, which is both an
atom and a list - nil is the empty list, ()
- nil is also used to represent the boolean value
false in LISP - LISP uses the atom t (or anything other than nil)
to represent true - There are several handy built-in functions for
working with lists - list creates a list
- gt (list 1 2 3)
- (1 2 3)
- length tells how many elements are in a list
- gt (length (1 2 3))
- 3
- null tells whether or not a list is empty
- gt (null (1 2 3))
- NIL
6Notes on Working with Lists
- Note that when you enter something like (null (1
2 3)) into an interpreter, you can see that the
list is not null - A program operates by manipulating lists, so you
would not normally know the results of a test
before you make it - Otherwise, you would be wasting your time
- Also note that our LISP interpreter (Gnu Common
LISP) always responds in all capital letters - LISP is not case sensitive, ie., xyz is
equivalent to XYZ - You may program using upper case or lower case
letters, but the interpreter will convert what
you write to all upper case
7More Built-in List Operations
- nth n, where n is an integer, will return the nth
element of a list - Note that the first element has n 0 and that
elements may be lists - gt (nth 2 (1 2 3))
- 3
- gt (nth 2 ((1 2) (3 4) (5 6) (7 8)))
- (5 6)
- member tests to see if its first argument is an
element of its second - gt (member a (x y z))
- NIL
- gt (member y (x y z))
- (Y Z)
- Note that member returns the rest of the list
beginning with the first argument, rather than a
simple t - This is useful if you want to continue processing
the list - Anything other than NIL is still treated as true
8Controlling Evaluation with Quote and Eval
- The interpreters default is to try to evaluate
everything - Ex (nth 2 (a b c d)) looks like it might
evaluate to c, but it wont - In evaluating (a b c d), LISP tries to apply the
operator a to the arguments b, c and d - Since a is not a defined operator, this is an
error - To get the desired behavior, use (nth 2 (quote
(a b c d))) or simply, (nth 2 (a b c d)) - eval undoes the effect of quote
- gt (quote ( 3 4))
- ( 3 4)
- gt (eval (quote ( 3 4)))
- 7
- The real advantage of eval is that it allows the
programmer to evaluate any constructed
S-expression that is, you can have your program
write and execute another program on the fly
9You Try It - What Will the Interpreter Respond?
- 1. (- 10 3)
- 2. (/ ( ( 4 5) 1) 2)
- 3. (nth 0 (list a b c))
- 4. (length (a b c))
- 5. (null ())
- 6. (member 1 (1 2 3))
- 7. (null (member z (x y z)))
- 8. (nth 3 ((a (b c)) d (e f g) (h) j k (l
(m n (o p))))) - 9. (list (- 5 2) ( 1 1))
- 10. (list (- 5 2) ( 1 1))
- 11. (eval (list 10 10))
- 12. ( (eval (list 10 10)) 20)
10Defining New Functions in LISP
- LISP has many built-in functions
- Programmer defined functions are created using
defun - Syntax (defun ltfunction namegt (ltparam namesgt)
ltfunction bodygt) - Example Convert Fahrenheit temperatures to
Celsius - (defun f-to-c (temp)
- (/ (- temp 32) 1.8)
- )
- Once this is defined, it can be used like any
other function - gt (f-to-c 32)
- 0.0
- gt (f-to-c 212)
- 100.0
11Conditional Expressions in LISP
- In any computer language, it is useful to be able
to branch based on different conditions - In C, we have if statements and switch
statements for this - In LISP, we have if statements and cond
statements - cond is most general, and therefore, used most
often - if is a nice shortcut, when you have only two
ways to branch - The basic structure of a cond is
- (cond (ltcondition1gt ltaction1gt)
- (ltcondition2gt ltaction2gt)
-
- (ltconditionngt ltactionngt)
- )
12Evaluating a Cond
- Conditions and actions (like everything else in
LISP) are S-expressions - Conditions are evaluated in order
- If no condition is true, the whole cond evaluates
to nil - As soon as a true condition is encountered, its
associated action is evaluated - All conditions following the first true one are
ignored - The value obtained for the action becomes the
value of the cond - A condition is true if it does not evaluate to
the empty list or nil - There are several handy predicates built in for
boolean tests - For numbers, we have relational operators lt, gt,
lt, gt, , and / - For numbers, we also have oddp, minusp, zerop
and plusp - To find out if we have a number or something
else, we can use numberp, listp, atom, null
13Example
- (defun minimum (x y)
return the minimum of two numbers - (cond ((lt x y) x)
if x lt y, return x - (( x y) x)
if x y, return x - ((gt x y) y)
if x gt y, return y - )
comments start with semi-colons! - )
lining up parens may aid clarity, at
first -
- This illustrates the use of cond, but the minimum
function can be improved - (defun minimum (x y)
- (cond ((lt x y) x)
- (t y)
t means otherwise, or by default
- )
use t only in the last line of a cond - )
14The if statement
- The if statement takes the form
- (if ltconditiongt lttrue-actiongt
ltfalse-actiongt) - When you only have a two-way test, you may prefer
if to cond - Example
- (defun minimum (x y)
- (if (lt x y) x y)
- )
15Boolean Operators
- The Boolean operators and, or and not are
available - Evaluation is short circuit
- For non-functional side effects, interpreters may
differ in evaluation - Examples
- gt (not nil)
nil is false, so not nil is true - T
- gt (not 3) 3
is not false, so not 3 is false - NIL
- gt (and (oddp 2) (print 2)) 2 is not
odd, so the second statement is - NIL
not evaluated (short circuit) - gt (or (oddp 2) (print 2)) here, a
side effect (printing) occurs, and - 2
the expression evaluates to what was - 2
printed (other interpreters might evaluate -
printing to nil)
16More on Output
- It is useful to be able to print output, even
though printing is a side effect, not a function,
and LISP is a functional language - Recall that printing output did not fit neatly
into Prolog, either - Programmers need to understand how it works,
anyway - More examples
- gt ( (print 2) (print 3))
- 2
- 3
- 5
- gt (print hello)
- HELLO
- HELLO
- gt (print Hello, world!)
- Hello, world!
- Hello, world!
17You Try It
- 1. Write a LISP function triple, that returns 3
times its input value - 2. Write a LISP function income-status, that
takes income and number of dependents as
arguments, and returns adequate if there is at
least 10,000 of income per dependent and
inadequate otherwise - (a) Use a cond statement
- (b) Use an if statement
18Database Example
- This example shows how to build and use a simple
database with fields for name, salary and
employee number - First, define a function to set up records
- (defun build-record (name salary emp-number)
- (list name salary emp-number)
- )
- Then, input records
- gt (build-record (Prof. Marling) 1000000
1) - ((Prof. Marling) 1000000 1)
- Next, define functions to access parts of records
- (defun name-field (record)
- (nth 0 record)
- )
19Database Example, continued
- (defun salary-field (record)
- (nth 1 record)
- )
- (defun emp-number-field (record)
- (nth 2 record)
- )
- (defun first-name (name)
- (nth 0 name)
- )
- Access fields of data in records
- gt (name-field ((Prof. Marling) 1000000 1))
- (Prof. Marling)
- gt (first-name (name-field ((Prof. Marling)
1000000 1))) - Prof.
20Database Example, continued
- Manipulate data
- (defun double-salary (record)
- (build-record
- (name-field record) ( 2 (salary-field
record)) (emp-number-field record) - )
- )
Actually, I added this function to the
example - gt (double-salary ((Prof. Marling) 1000000 1))
- ((Prof. Marling) 2000000 1) I really
like this function (- - Note that the functional programming style does
not actually change the original record. Rather,
it creates a new record with the desired changes.
This is not like C, but it is like ordinary
mathematical functions, e.g., the square root.
The square root of 9 is 3, but the original 9
maintains its value.
21Lists as Recursive Structures
- Manipulating lists is central to LISP
- Recursion is a natural control structure for
moving about in lists - There are three basic list processing functions,
car, cdr and cons - Some versions of LISP rename these functions, but
the idea behind list processing remains the same - car takes one argument, which must be a list, and
returns the first element of the list - car stands for Contents of the Address Register,
which was a sensible name for an important
function in 1959 - Examples
- gt (car (I love LISP))
- I
- gt (car ( (Absolutely everybody) (loves
LISP))) - (ABSOLUTELY EVERYBODY)
22cdr
- cdr takes one argument, which must be a list, and
returns the rest of the list except for the car - It always returns a list, even if theres nothing
or only one thing left in the list - cdr stands for Contents of the Decrement
Register, another name that was more mnemonic in
1959 - Examples
- gt (cdr (I really love LISP))
- (REALLY LOVE LISP)
- gt (cdr (Hello world))
- (WORLD)
- gt (cdr ())
- NIL
23More on List Processing Functions
- car and cdr are used together to get to any part
of a list - gt (car (cdr (computer TV camera)))
- TV
- gt (car (cdr (car (cdr ((a b)(c d)(e
f)))))) - D
- Note that evaluation proceeds from the inside
out, as it would for any mathematical function - cons takes two arguments, a new list element and
a list to add it to - cons stands for list constructor
- gt (cons a (b c))
- (A B C)
- gt (cons (a b) (c d))
- ((A B) C D)
24Using List Processing Functions
- The three basic functions are used to build
additional functions - For example, if nth were not already built-in, we
could write it - (defun nth (n a-list)
- (cond ((zerop n) (car a-list))
- (t (nth (- n 1) (cdr
a-list))) - )
- )
- Here, we assume that n is a positive number and
that a-list actually is a list we could add
error checking, if desired - Note the natural use of recursion!
- Many LISP functions work by processing the first
element in a list and then processing the rest of
the list - This is called tail recursion
25Another Example of Tail Recursion
- filter-negatives uses tail recursion to process a
list of numbers and return a list containing only
the positive numbers - Example
- gt (filter-negatives (-1 0 1 2 -2 3))
it also filters out zeroes - (1 2 3)
- (defun filter-negatives (number-list)
- (cond ((null number-list) nil)
termination condition - ((plusp (car number-list)) (cons
(car number-list) -
(filter-negatives (cdr
number-list)))) - (t (filter-negatives (cdr
number-list))) - )
- )
26You Try It
- Write function double-list which doubles every
number in a given list - Assume that the argument will be a valid list of
numbers
27Double Recursion
- Because lists may be nested, tail recursion cant
fully process all lists - Double recursion is another standard technique
- Example If we want to know how many elements are
in a nested list, we can use length. If we want
to know how many atoms are in a nested list, we
need a new function, that uses double recursion - (defun count-atoms (my-list)
- (cond ((null my-list) 0)
- ((atom my-list) 1)
- (t ( (count-atoms (car my-list))
- (count-atoms (cdr
my-list)))))) - (count-atoms ((1 2) 3 (((4 5 (6))))))
- 6
28Finding the Length of a List Using Tail Recursion
29Counting Atoms in a List With Double Recursion
30Binding Variables Using set
- While purely functional languages do not have
assignment statements, set, setq and setf are
ways of creating creating global variables and
assigning values to them - set and setq serve the same purpose
- setq is the more commonly used function
- Example gt (setq x 0)
- 0
- If no global variable named x exists, this will
create one and set its value to 0 - If there is already a global variable x, this
will change its value to 0 - Note that the first argument to setq must be a
symbol - The second can be any S-expression
- setq may also be used to assign a value to a
functions argument within a function body or to
a local variable - In this case, no new global variable is created
31Example of setf
- To assign a value to any arbitrary memory
location, setf can be used instead of setq - Its use is usually discouraged, as it detracts
from functional programming and makes it harder
to debug programs - However, it does provide flexibility
- gt (setf x (a b c))
- (A B C)
- gt x
- (A B C)
- gt (setf (car x) 1)
- 1
- gt x
- (1 B C)
- gt(setf (cdr x) (2 3))
- (2 3)
- gt x
- (1 2 3)
32Defining Local Variables with let
- A let block establishes an environment for local
variables - If there are global variables with the same
names, their values are used outside of the let
block - A let block takes the form
- (let (ltlocal-variablesgt) ltexpressionsgt)
- A local variable may be a symbol or a list
containing a symbol and a value - Symbols without values are set to NIL by default
- Examples
- gt (let ( (a 1) (b 2) )
gt (let (a b) - ( a b)
(setq a 1) - )
(setq b 2) - 3
( a b) -
) -
3