mapping).
So far we've seen three ways in which a value can be associated with a
variable in LISP:
Some variables, such as the names of built-in procedures, are
predefined. When Allegro Common LISP starts up, these variables
(
The programmer can introduce a new binding by means of a
definition. A definition may introduce a new name for a
previously computed value, or it may give a name to a newly constructed
value.
When a programmer-defined procedure is called, the parameters
of the procedure are bound to the values of the corresponding arguments in
the procedure call. Unlike the other two kinds of bindings, parameter
bindings are local -- they apply only within the body of the
procedure. LISP discards these bindings when it leaves the procedure and
returns to the point at which it was called.
This binding list contains two binding specifications -- one in which the
value of the expression
When a
What are the values of the following
You may use Allegro Common LISP to help you answer these questions, but be
sure you can explain how it arrived at its answers.
Using a
In this procedure, the
Consider the following Local Variables and Scope
The first part of this lab reviews two new, but related, mechanisms to perform
this association:
cons and truncate, for example) are already
bound to the procedures they denote.
((next (car source)) (char-list '()))
(car source) is bound to the symbol
next, and the other in which the empty list is bound to the
symbol char-list. Notice that binding lists and binding
specifications are not procedure calls; their role in a
let-expression is structural.
let-expression is evaluated, the first thing that
happens is that the expressions in all of its binding specifications are
evaluated and collected. Then the symbols in the binding specifications
are bound to those values. Next, the expressions making up the body of the
let-expression are evaluated, in order; the value of the last
expression in the body becomes the value of the entire
let-expression. Finally, the local bindings of the variables
are canceled. (Variables that were unbound before the
let-expression become unbound again; variables that had
different bindings before the let-expression resume those
earlier bindings.)
let-expressions?
(let ((tone "fa") (call-me "al"))
(concatenate 'string call-me tone "l" tone))
;; solving the quadratic equation x^2 - 5x + 4
;;
(let ((discriminant (- (* -5 -5) (* 4 1 4))))
(list (/ (+ (- -5) (sqrt discriminant)) (* 2 1))
(/ (- (- -5) (sqrt discriminant)) (* 2 1))))
(let ((sum (+ 8 3 4 2 7)))
(let ((mean (/ sum 5)))
(* mean mean)))
let-expression often simplifies an expression that
contains two or more occurrences of the same subexpression. The programmer
can compute the value of the subexpression just once, bind a variable to
it, and then use that variable whenever the value is needed again.
Sometimes this speeds things up by avoiding such redundancies as the
recomputation of the discriminant in 1(b) above; in other cases, there is
little difference in speed, but the code may be a little clearer. For
instance,consider the following remove-all procedure that
deletes all occurrences of an item from a list:
(defun remove-all (item ls)
(if (null ls)
'()
(let ((first-element (car ls))
(rest-of-result (remove-all item (cdr ls))))
(cond ((equal first-element item) rest-of-result)
((listp first-element)
(cons (remove-all item first-element) rest-of-result))
(else (cons first-element rest-of-result))))))
let allows us to evaluate (car
ls) and (remove-all item (cdr ls)) just once. Giving
each value a name also makes it a little easier to understand what the
three cond-clauses are doing.count-all-symbols procedure which
counts the number of symbols that appear in a list or any of its sublists.
A sample run also is given:
(defun count-all-symbols (ls)
(cond ((null ls) 0)
((symbolp (car ls)) (+ 1 (count-all-symbols (cdr ls))))
((listp (car ls))
(+ (count-all-symbols (car ls)) (count-all-symbols (cdr ls))))
(else (count-all-symbols (cdr ls)))))
(count-all-symbols '(((a b) c) d (e (f)))) ===> 6
Example 1c above shows that it is possible to nest one
let-expression inside another. One might be tempted to try to
combine the binding lists for the nested let-expressions,
thus:
;; Combining the binding lists doesn't work!
;;
(let ((sum (+ 8 3 4 2 7))
(mean (/ sum 5)))
(* mean mean))
This wouldn't work (try it and see!), because, within a
let-expression, all of the expressions are evaluated
before any of the variables are bound. Specifically, LISP will
try to evaluate both (+ 8 3 4 2 7) and (/ sum 5)
before binding either of the variables sum and
mean; since (/ sum 5) can't be computed until
sum has a value, an error occurs. You have to think of the
local bindings coming into existence simultaneously rather than one at a
time.
Because one often needs sequential rather than simultaneous binding, LISP
provides a variant of the let-expression that rearranges the
order of events: If one writes let* rather than
let, each binding specification in the binding list is
completely processed before the next one is taken up:
;; Using let* instead of let works!
;;
(let* ((sum (+ 8 3 4 2 7))
(mean (/ sum 5)))
(* mean mean))
The star in the symbol let* has nothing to do with
multiplication; just think of it as an oddly shaped letter.
Write a nested let-expression that binds a total of five
variables, a, b, c, d,
and e, with a bound to 9387 and each subsequent
variable bound to a value twice as large as the one before it --
b should be twice as large as a, c
twice as large as b, and so on. The body of the innermost
let-expression should compute the sum of the values of the
five variables.
Write a let*-expression equivalent to the
let-expression in the previous exercise.
One can use a let- or let*-expression to create a
local name for a procedure:
(defun hypotenuse-of-right-triangle (first-leg second-leg)
(let ((square (lambda (n)
(* n n))))
(sqrt (+ (square first-leg) (square second-leg)))))
Regardless of whether square is defined outside this
procedure, the local binding gives it the appropriate meaning in the body
of the let-expression.
Review the following code to determine what is printed:
[user]: (setf x 'outside)
[user]: (let ((x 'inside)
(y x))
(list x y))
[user]: (let* ((x 'inside)
(y x))
(list x y))
Be sure you understand the result of each of these statements
(define longer-string (str1 str2)
(if (< (string-length str1) (string-length str2))
str2
str1))
Use this function in the definition of a procedure longest-on-list
which takes as its argument any non-empty list of character strings and
returns whichever element of that list is the longest:
(longest-on-list '("This" "is" "the" "forest" "primeval")) ===> "primeval"
(longest-on-list '("Wherefore" "art" "thou" "Romeo")) ===> "Wherefore"
(longest-on-list '("To" "be" "or" "not" "to" "be")) ===> "not"
(longest-on-list '("foo")) ===> "foo"
Feel free to use let or let* as needed.
As in procedural programming in other languages, LISP allows us to perform
some procedure call or sequence of procedure calls repeatedly, for the sake
of the side effects on structures or variables or for input or output. For
example, the printing of the table of numbers and their square roots
involves the repeated output of a line of a number and its root. LISP
provides a special expression type to capture this form concisely and
efficiently: the do-expression. A do-expression
has the following structure:
(do loop-control-list
(exit-test postlude)
body)
The loop-control-list sets up bindings for any local variables
that the do-expression will use; almost always, there is at
least one ``loop control variable'' that counts off repetitions of the loop
or positions in a vector or some such thing. A loop-control list is a list
of zero or more loop-control specifications, one for each variable
that is to be bound locally. A loop-control specification is a list in
which
let-expression),
The exit-test is a single expression that is evaluated to
determine when enough iterations have been performed; the loop is finished
as soon as the value of exit-test is anything other than
false (NIL).
The postlude, which is optional, is a sequence of expressions
to be evaluated after the exit test has succeeded; the value of the last of
these expressions becomes the value of the entire
do-expression. (If the postlude is omitted, the value of the
do-expression is unspecified and only its side effect is of
interest.)
The body, which is also optional, is a sequence of expressions to be evaluated, in order, on each iteration. All of the expressions in the body are evaluated only for their side effects; the values are discarded.
Here's a simple example of a do-expression. We write a
procedure display-countdown that takes one argument, a
non-negative integer, and prints out the positive integers equal to or less
than its argument, in descending order, one per line. Thus, the
interaction might look as follows:
[user]: (display-countdown 3) 3 2 1 Blast off!The following code demonstrates a simple solution to this problem:
(defun display-countdown (start)
(do ((remaining start (- remaining 1)))
((zerop remaining) "Blast off!")
(print remaining)))
Let's trace through the execution of a call to this procedure, using the
above example (display-countdown 3):
do-expression has just one loop-control variable in this
case, namely remaining; the first thing that happens is that a
local binding for remaining is created, with the initial value
of start, which is 3. The updating expression (-
remaining 1) is ignored at this point, because we haven't completed
any iterations of the loop yet.
(zerop remaining), is evaluated. Since 3
is not zero, the exit is not taken.
(print remaining). LISP's
print procedure moves to a new output line and prints the number
3. (If we used (princ remaining) instead, all output would
appear on one line by itself.) This completes the first iteration.
(- remaining 1) is 2; this becomes the new value
of remaining for the next iteration.
remaining is updated again, changing
from 2 to 1. The exit test comes out false (NIL) again, since
1 is not zero; the loop body is evaluated once more, so 1 is printed on a
line by itself. This completes the third iteration.
remaining changes its value from 1 to
0. When the exit test is evaluated this time, its value is t,
so the loop is finished.
"Blast off!"; this
string is returned as the value of the do-expression, and
hence the value of the procedure call.
display-count that takes two arguments,
start and finish, and counts upwards from
start to finish, displaying each number on a
separate line. (The preconditions are that start and
finish must both be exact integers and start must
be less than or equal to finish.) The
display-count procedure should return the number of lines of
output it produces.
sqrt-table procedure that takes one argument, a
non-negative integer, and prints out a table of integers and their square
roots, for integers equal to or less than its argument. A sample
interaction follows:
[user]: (sqrt-table 5) Number Square Root 1 1.00000 2 1.41421 3 1.73205 4 2.00000 5 2.23607Use a
do-expression to control the repeated computations.
In the next example of a do-expressions,
a procedure takes a list as argument and returns the list in
reverse order.
(defun reverse-list (ls)
(if (listp ls)
(do ((new-ls '() (cons (car old-ls) new-ls))
(old-ls ls (cdr old-ls)))
((null old-ls) new-ls)
)
)
)
[user]: (reverse-list '(3 1 4 1 5 9))
(9 5 1 4 1 3)
This time there are two loop-control variables: new-ls
provides a list of elements already processed (starting with nil,
while old-ls contains the list elements not yet processed.
The iteration moves an element from old-ls to
new-ls as part of the updating of variables. the body of the
do-expression is null.
do-expression to run through the positions of
the list.
sum procedure, which takes any list of numbers as its
argument and returns their sum. In your procedure, use a
do-expression to process list elements iteratively.
Overall,
Do-expressions can be used for clarity and concision in many
of the situations.
Here, the line to a indicates that this is the head of the list.
The diagonal line through the right half of the rectangle indicates that
nothing comes later in this list. Since (cons 'a '()) gives
the list (a), this diagram represents (a) as well.
Now consider the list (cons 'b '(a)) or (b a). Here, we
draw another rectangle, where the head points to b and the tail
points to the representation of (a) that we already have seen.
The result is:
Similarly, the list (d c b a) is constructed as
A similar approach may be used for lists, which have components which
are sublists. For example, consider the list ((a) b (c d) e)
This is a list with four components, so at the top level we will need
four rectangles, just as in the previous example for the list (d c b
a). Here, however, the first component designates the list
(a), which itself involves the box-and-pointer diagram already
discussed. Similarly, the list (c d) has two boxes for its two
components (just as we discussed for (b a) earlier). The
resulting diagram follows:
Throughout these diagrams, the null list is represented by a null pointer
or line. Thus, the list containing the null list, ( ( ) ) - that
is (cons '() '()) - is represented by a rectangle with lines
through both halves:
To represent such an expression on paper or as a LISP printout, dot
notation is used: (a . b) Here, the dot indicates that
cons has been applied, but the second argument is not a list.
Similarly,
One way to write such a directory in LISP is to consider each entry as a
two-element list, such as ("walker" 4208) or ("stone"
3181). An entire directory, then, could be considered as a list of
such entries:
As the telephone directory example illustrates, a particularly common
application of association lists involves looking for a desired name or
first element of a pair and retrieving the second element of a pair. Thus,
the first element of each pair (the car of a pair) often is called
a key value, and the remained or the pair is its associated
data. For example, in the above illustration, "herman",
"stone", "walker" are the keys, and the numbers are the
associated data. As this example suggests, association lists are a simple
way to implement small databases.
Since such applications are so common, LISP provides procedures to
retrieve a pair containing a desired key. The most frequently used such
procedure is assoc. Given a key and association list,
assoc returns the first pair with the given key. If the key
does not match any key, then assoc returns false (#f).
For example,
Since such directories typically contain several entries, this is a
particularly good circumstance to use a setf expression, so a
symbol can represent the entire directory:
To find the telephone number corresponding to a given name, we could apply
the cadr procedure to the result of assoc:
To change the test for equality, you can specify the predicate as part of
the call to
This document is available on the World Wide Web as
Box and Pointer Representation
As we have seen, LISP uses cons to build lists. The result of a
cons procedure commonly is represented as a rectangle - divided in
half. From the first half of the rectangle, we draw an arrow to the head
of a list; from the second half of the rectangle, we draw an arrow to the
rest of the list. For example, (cons 'a '()) would be represented
as follows:

(cons 'd (cons 'c (cons 'b (cons 'a '())))) and would be drawn as
follows:



While we consistently have discussed cons in the context of lists,
LISP allows cons to be applied when the second element is not a
list. For example, (cons 'a 'b) is a legal expression which can
be represented by the following box-and-pointer diagram:
((x) y z)
(x (y z))
((a) b (c ()))

(cons 1 'a) may be written (1 . a) and
(cons "Henry" "Walker") produces
("Henry" . "Walker").
Using a box-and-pointer representation, this last result would be drawn as
follows:
(cons 'a "Walker")
(cons 'a '())
(cons '() 'a)
(cons '() '())
Mapping: Assocication Lists
Consider the organization in a simple telephone book. Overall, a telephone
directory consists of a sequence of entries, and each entry in the book
includes a name and a telephone number.
( ("herman" 4202) ("stone" 3181) ("walker" 4208) )
In LISP, such a list of pairs is called an association list or
alist.
(assoc 'stone '((herman 4202) (stone 3181) (walker 4208)))
returns (stone 3181), while .
(assoc 'jepsen '((herman 4202) (stone 3181) (walker 4208)))
returns NIL.
(setf telephone-directory
'( (herman 4202)
(stone 3181)
(walker 4208)
)
)
With such a definition, (assoc 'stone telephone-directory)
returns (stone 3181), and (assoc 'jepsen
telephone-directory) returns NIL.
(cadr (assoc 'stone telephone-directory))
returns 3181.
LISP contains at least four notions of equality for use in testing:
(Lincoln "February 12, 1809") or
(Lincoln (February 12 1809)).
For most LISP interpreters, eq checks if two items are identical objects.
eql checks if two items are identical objects or if the two
items are numbers with the same value.
equal checks if two items are structurally similar (roughly,
their printed representations are the same).
equalp checks if two items are equal or the
characters they contain are the same, ignoring capitalization.
assoc uses eql in its
test of equality, so that keys involving strings will never match in a
look-up. For example, (assoc "Herman" directory) could never
find a match, regardless of how directory was defined, as "Herman" would
never satisfy an eql test.) assoc to be (assoc "Herman"
telephone-directory :test #'equal).
telephone-directory with the faculty names as strings:
setf telephone-directory
'( ("herman" 4202)
("stone" 3181)
("walker" 4208)
)
)
Now, observe the results from the following statements:
[user]: (assoc "herman" telephone-directory)
[user]: (assoc "herman" telephone-directory :test #'equal)
[user]: (assoc "Herman" telephone-directory :test #'equal)
[user]: (assoc "Herman" telephone-directory :test #'equalp)
Then use assoc to look up names using equal and
using the default eql. Be sure you understand why each result
occurs.
(assoc 4202
telephone-directory).)
(assoc 'Adams birthdays)).
http://www.math.grin.edu/~walker/courses/261/lab-beginning-LISP-3.html
created January 25, 1998
last revised January 25, 1998