Before getting into the lecture material, we talked about some midterm
problems:

Minimax (this was already on the newsgroup)

After grading another of the midterm problems, it's apparent to me that
I failed to do a good job explaining the minimax algorithm.  Since this
algorithm is integral to hw7, I thought I'd post a solution/explanation
to this midterm problem ASAP, in the hopes that people might "get it"
better this time around.  I'll also devote some lecture time Friday to
reviewing the algorithm.

   Starting board:                          A
                                          /   \
                                        /       \
                                      /           \
                                    /               \
                                  /                   \
   My possible moves:           B                       C
                              /   \                   /   \
                            /       \               /       \
   His counter-moves:     D           E           F           G
                         / \         / \         / \         / \
                        /   \       /   \       /   \       /   \
   My next moves:      H     I     J     K     L     M     N     O
                      
                       4     6     8     5     2     4     5     1
   
   (i) Using the basic minimax algorithm, write the values that will be
   given to all of the boards A-G.  (Just put the value to the right of
   each board.)
   
Thus the tree with all values is:

   Starting board:                          A  6
                                          /   \
                                        /       \
                                      /           \
                                    /               \
                                  /                   \
   My possible moves:           B  6                    C  4
                              /   \                   /   \
                            /       \               /       \
   His counter-moves:     D  6        E  8        F  4        G  5
                         / \         / \         / \         / \
                        /   \       /   \       /   \       /   \
   My next moves:      H     I     J     K     L     M     N     O
                      
                       4     6     8     5     2     4     5     1

   (ii) Which move, B or C, should I choose, based on the results of the
   minimax algorithm?

Cleary B; almost everyone got this right on the exam.
   
   (iii) List the boards which I would not have to bother evaluating if I
   use alpha-beta pruning in my minimax search.

Here the answer is "K" _and_ the whole "G-N-O" branch.  This is hard to
explain with ascii-art, so I'll go over this example in class.

Find-all-paths

           F          A          B
           *----------*----------*
                      |         /|
                      |        / |
                      |       /  |
                      |      /   |
                    C *     /    |
                      |    /     |
                      |   /      |
                      |  /       |
                      | /        |
                      |/         |
                    E *----------* D

  (find-all-paths 'F 'E)

should return

   ((F A B D E) (F A B E) (F A C E))

I chose an adjacency list:

   (defconstant *graph* '(
      (f a)
      (a b c f)
      (b a d e)
      (c a e)
      (d b e)
      (e b c d)))

but many representations are possible, provided you can write a function
like:

   (defun neighbors (node)
      "Returns a list of neighbors of NODE"
      (rest (assoc node *graph*)))

which is needed to solve the rest of the problem.

   (defun find-all-paths (start goal)
      (dfs start goal nil))
   
Basically we can divide the main algorithm into three pieces:
 - if we're done, return the path
 - check for cycles
 - else recurse on all our neighbors (take one step closer)

   ;;; Recursive version
   
   (defun dfs (start goal history)
      "Return a list of all paths from START to GOAL using a dfs"
      (cond ((eql start goal) (list (reverse (cons goal history))))
            ((member start history) nil)
            (t (try-all-paths (neighbors start) goal (cons start history)))))
   
   (defun try-all-paths (new-starts goal history)
      (cond ((null new-starts) nil)
            (t (append (dfs (first new-starts) goal history)
                       (try-all-paths (rest new-starts) goal history)))))
   
Note that changing APPEND to OR turns it back into a normal DFS.
   
   ;;; Applicative version
   
   (defun dfs (start goal history)
      "Return a list of all paths from START to GOAL using a dfs"
      (cond ((eql start goal) (list (reverse (cons goal history))))
            ((member start history) nil)
            (t (apply #'append
                      (mapcar #'(lambda (next)
                                    (dfs next goal (cons start history)))
                              (neighbors start))))))
   
Finally, an alternate version which passes the results around as it
recurses (rather than letting the results flow up as return values):

   (defun find-all-paths (start goal)
      (dfs start goal nil nil))
   
   (defun dfs (start goal history paths-found)
      "Return a list of all paths from START to GOAL using a dfs"
      (cond ((eql start goal) (cons (reverse (cons start history)) paths-found))
            ((member start history) paths-found)
            (t (try-all-paths (neighbors start) 
                              goal 
                              (cons start history)
                              paths-found))))
   
   (defun try-all-paths (new-starts goal history paths-found)
      (cond ((null new-starts) paths-found)
            (t (dfs (first new-starts) 
                    goal 
                    history
                    (try-all-paths (rest new-starts) 
                                   goal 
                                   history 
                                   paths-found)))))
   
That way is a little messier, in my opinion, but it works.

(New material from here down.)

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.


Lecture notes by Kurt Eiselt, 1998.
Minor changes / additions by Brian McNamara, 1998.
Last updated on Fri Aug 14 12:28:59 EDT 1998 by Brian McNamara