"...might as well be walkin' on the sun."
Here's the lecture you would have heard had the air conditioning
in the lecture hall actually been working:
Iteration
So far, we've seen two different ways of controlling program
flow. The first was recursion, and the second was what we
called applicative programming. They're both very
functional. But now that you know about variables and
assignment, you're finally ready to learn about iteration.
Common LISP provides you with two very simple iterative
forms. The first is called "dotimes" and looks like this:
(dotimes ([variable] [limit-expr] [result-expr])
:
:
[body of code]
:
:
)
[variable] is a symbol. You specify the name, and LISP
initializes the variable to 0. It is not evaluated.
[limit-expr] is an expression which is evaluated; LISP
expects a number to be returned here.
[result-expr] is an optional argument. When the iteration is
finished, this expression is evaluated and the result is
returned by "dotimes". If no expression is found here,
"dotimes" returns nil when it is done.
This algorithm describes the operation of "dotimes":
1. [variable] := 0
2. if [variable] = [limit-expr]
then return [result-expr] (or nil if no [result-expr])
3. execute body
4. [variable] := [variable] + 1
5. go to 2
See, it's very simple. Don't you wish I had let you use this
earlier? I don't. Anyway, here's an example of using
"dotimes" in, what else, a factorial function:
(defun factorial (number)
(let ((result 1))
(dotimes (counter number result)
(setf result (* result (+ 1 counter))))))
The second simple iterative form is "dolist", which looks
like this:
(dolist ([variable] [list-expr] [result-expr])
:
:
[body of code]
:
:
)
[variable] is a symbol. You specify the name, and LISP binds
this symbol to successive elements of the list given in
[list-expr]. [variable] is not evaluated.
[list-expr] is an expression which is evaluated; LISP expects
a list to be returned here.
[result-expr] is an optional argument. When the iteration is
finished, this expression is evaluated and the result is
returned by "dolist". If no expression is found here,
"dolist" returns nil when it is done.
This algorithm describes the operation of "dolist":
1. if there's no next element of [list-expr]
then return [result-expr] (or nil if no [result-expr])
2. [variable] := next element of [list-expr]
3. execute body
4. go to 1
Note that [list-expr] isn't changed while "dolist" is
running---it doesn't "shrink" by one element each time the
body of code is executed. Here's an example of "dolist" in
"my-remove":
(defun my-remove (item r-list)
(let ((result nil))
(dolist (element r-list result)
(cond ((not (eql element item))
(setf result (append result (list element))))))))
There's another iterative form which is not so simple, at
least in my opinion. It's just called "do", and it looks like this:
(do (([var-1] [init-expr-1] [update-expr-1])
([var-2] [init-expr-2] [update-expr-2])
:
:
([var-n] [init-expr-n] [update-expr-n]))
([test-expr] [action-expr-1] ... [action-expr-m])
:
[body of code]
:
)
Here's how "do" works:
1. each [var-i] is bound to its corresponding [init-expr-i]
(in "parallel", just like with "let"---that is, there's
no guarantee of the order the bindings are completed, so
don't encode dependencies of any kind in this part of the
"do" form).
2. [test-expr] is evaluated. If the result is non-nil, then
the [action-expr-1] through [action-expr-m] are evaluated
in left-to-right fashion. The "do" form returns the
value of [action-expr-m].
3. if evaluating [test-expr] returns nil, then execute the
body of code.
4. when the body of code has been executed, update each
[var-i] by binding it to the value obtained by evaluating
the corresponding [update-expr-i]. The [update-expr-i]
are optional, so if it's not there, then the various
[var-i] are never changed. Also, these bindings are done
automatically by "do", so there's no need to include
your own "setf" or "setq".
5. go to 2
If you use
(return)
or
(return [expr])
in the body of a "do" or "dotimes" or "dolist", the iterative
form will be exited immediately, and will return nil or
[expr] accordingly.
Weird science
With this powerful "do" form, you can write functions where
all the work is done in the "preamble":
(defun factorial (n)
(do ((count n (- count 1))
(result 1 (* result count)))
((= 0 count) result)
;; no body!
))
Some authors of LISP texts (who shall remain nameless) think
that this is elegant programming style. Frankly, it makes my
head hurt. Use it if you want to, but please don't ask me
to debug it.
Other forms of iteration
Another iteration mechanism is the "loop" macro (we'll see
more about macros soon). It has been added to the Common
LISP standard as described in the second edition of Steele,
but it isn't in the first edition. Some LISP folks complain
that the loop macro isn't defined all that well in the
second edition, which leads to some ambiguities. Consequently,
we don't cover it in this class, but feel free to look it up.
And while you're thinking about things iterative, grab
a LISP book and look up the granddaddy of all LISP
iterative forms: "prog". Then once you've read about it,
remember to try to avoid its use. The inclusion of the
"goto" in prog means you want to try to exclude it from
your code in most cases.
The lexical closure
Let's take another look at the notion of process and state.
Let's say that I want to construct a function that simulates
a Coke (TM) machine. To do this, I'll need a state variable
to tell me how many cans of Coke there are in this machine.
I could do this:
(defvar number-of-cans 5)
and then my function could be this:
(defun get-coke ()
(cond ((> number-of-cans 0)
(setf number-of-cans (- number-of-cans 1))
(print "have a Coke"))
(T (print "sorry, out of Coke"))))
I can now call "get-coke" five times, and I'll see the same
message ("have a Coke"), but on the sixth call, I'll see a
new message ("sorry, out of Coke"). I now have a function
which knows its history -- it has state.
However, "number-of-cans" is a free variable -- that is, it's
initialized outside the lexical scope of the function in
which it's referenced, and I don't like that. In this case,
it's a global variable that's modifiable by all functions except
where that name is also being used as a local or lexically-
scoped variable, and that's a situation that's ripe for
trouble. I'd really like this variable to be local to just
one specific Coke machine. And I'm sure I don't want to have
to create a separate global variable for each simulated Coke
machine, especially if I'm going to simulate the behavior of
some vending machine company that may own hundreds or
thousands of individual Coke machines. Keeping track of all
that could be difficult, to say the least.
As you know, to create additional local variables beyond
those that are given in a function's argument list, I can use
LISP's "let" form. So my revised "get-coke" might look like
this:
(defun get-coke ()
(let ((number-of-cans 5))
(cond ((> number-of-cans 0)
(setf number-of-cans (- number-of-cans 1))
(print "have a Coke"))
(T (print "sorry, out of Coke")))))
Now, "number-of-cans" is lexically-scoped within the confines
of the "let" form, so no other function can clobber it. But
it doesn't work the way I want it to, because each time I
call "get-coke", my local state variable "number-of-cans" is
reset to the value 5. Waaahhh!!!
Rethinking this, I decided that what I'd really really like
to do is associate a variable with my Coke machine simulation
such that:
1. It's local to the function, so nobody else can mess
with it.
2. The state is saved in this variable, even after the
function has been exited, and the next time I call this
function, the state variable contains exactly what was
left in it when this function was last exited.
How can I do this? We need to take advantage of lexical
scoping: we can combine the lexically-scoped variable of the
"let" form with our "lambda" form that we learned about
earlier, and I'll throw in the "function" function.
I'll use pretty much the same code as above, but
this new function, which I'll call "make-coke-machine"
instead of "get-coke", doesn't have the same purpose as "get-
coke". (I'll show you what "get-coke" looks like soon.)
When I called "get-coke", I expected a simulation of
a Coke machine -- it would either give me a virtual Coke or
it wouldn't. But when I embed that same code inside a
"lambda" form, which is in turn embedded inside a "function"
form, I get a function which when called returns *another*
function, which will then behave like a Coke machine when it
is evaluated:
(defun make-coke-machine ()
(let ((number-of-cans 5))
(function (lambda ()
(cond ((> number-of-cans 0)
(setf number-of-cans
(- number-of-cans 1))
(print "have a Coke"))
(T (print "sorry, out of Coke")))))))
Now, evaluating "make-coke-machine" returns an executable
function object with a built-in lexically-scoped variable,
"number-of-cans", initially bound to the value 5. For all
intents and purposes, it is a special storage place known
only to this particular instantiation of the function.
Why does it work? When I define a lambda function and then
use "function" to return the executable function object, the
LISP system must save copies of bindings of any free
variables within the lambda function at the time that the
surrounding "function" was evaluated -- lexical scoping
demands it. Since "number-of-cans" is a free variable (again,
it's not defined in the argument list or in a "let" form
within the lambda function -- it is a global variable from
the point of view of the lambda function), LISP binds that
variable to the value 5 and internalizes that when it creates
the function object.
After evaluating "make-coke-machine", I can do this:
? (setf cs-machine (make-coke-machine))
#[COMPILED-LEXICAL-CLOSURE #x5B840E]
and I'll have a simulated Coke machine with its own name,
"cs-machine", and its own internal state variable. Now I'll
create a function which will allow me to use my simulated
Coke machine:
? (defun get-coke (machine-name)
(funcall machine-name))
GET-COKE
and that in turn will allow me to get a simulated Coke from
my simulated Coke machine in the simulated computer science
building:
? (get-coke cs-machine)
"have a Coke"
"have a Coke"
Why do we see "have a Coke" twice? I'll leave that as an
exercise for you.
There should be four more Cokes left in my machine.
? (get-coke cs-machine)
"have a Coke"
"have a Coke"
? (get-coke cs-machine)
"have a Coke"
"have a Coke"
? (get-coke cs-machine)
"have a Coke"
"have a Coke"
? (get-coke cs-machine)
"have a Coke"
"have a Coke"
? (get-coke cs-machine)
"sorry, out of Coke"
"sorry, out of Coke"
Sure enough, I could get five Cokes, but on the sixth try,
the machine told me that it was empty. At any time, I could
have created another simulated Coke machine, which would have
its own independent local state variable:
? (setf ee-machine (make-coke-machine))
#[COMPILED-LEXICAL-CLOSURE #x5BC4F6]
? (get-coke ee-machine)
"have a Coke"
"have a Coke"
I can show that the state variable in the "ee-machine"
simulator is independent of the state variable in the "cs-
machine" simulator, because even though there are Cokes in
the "ee-machine", the "cs-machine" is still empty:
? (get-coke cs-machine)
"sorry, out of Coke"
"sorry, out of Coke"
And I can show that the state variable is untouchable just by
doing this:
? number-of-cans
> Error: Unbound variable: NUMBER-OF-CANS
> While executing: SYMBOL-VALUE
> Type Command-/ to continue, Command-. to abort.
> If continued: Retry getting the value of NUMBER-OF-CANS.
See the RestartsI menu item for further choices.
1 >
These functions that I have created with their own internal
local state variables are called "lexical closures".
Sometimes these things are called "generators". They can
have multiple local variables, and you can also pass values
to them via parameters at the time they are created, and
those values will be internalized too.
A lexical closure is a great tool for simulating independent
real-world objects where supply and demand is an important
issue. Examples are robots, operating systems, grocery store
checkout lines, automated teller machines, pumps at the gas
station, and yes, vending machines.
Copyright 1998 by Kurt Eiselt. All rights reserved.
Last revised: May 21, 1998