WARNING : You must move to the advanced language level in DrScheme
starting with this lecture.

Let's return to our find-flights program.  This version is a bit
cleaned up from the previous one in the notes.

;; find-flights : city city -> (listof city) or false
;; creates a path of flights from start to finish
(define (find-flights start finish)
  (local [(define rm ...)
	  (define (find-helper start finish visited)
	    (cond [(symbol=? start finish) (list start)]
		  [(memq start visited) false]
		  [else (local
			 [(define possible-route
			    (find-flights/list (direct-cities start)
					       finish 
					       (cons start visited)))]
			 (cond [(boolean? possible-route) false]
			       [else (cons start possible-route)]))]))

	  ;; direct-cities : city route-map -> (listof city)
	  ;; return list of all cities in route map with direct flights from
	  ;; given city
	  (define (direct-cities from-city)
	    (local [(define from-city-info
		      (filter (lambda (c) (symbol=? (city-info-name c) from-city))
			      rm))]
		   (cond [(empty? from-city-info) empty]
			 [else (city-info-fly-to (first from-city-info))])))
          
	  ;; find-flights/list :
	  ;;      (listof city) city (listof city) -> (listof city) or false
	  ;; finds a flight route from some city in the input list to the
	  ;; destination, or returns false if no such route exists
	  (define (find-flights/list loc finish visited)
	    (cond [(empty? loc) false]
		  [else (local [(define possible-route
				  (find-helper (first loc) finish visited))]
			  (cond [(boolean? possible-route)
				 (find-flights/list (rest loc) finish visited)]
				[else possible-route]))]))]
    (find-helper start finish empty)))

The airline that asked for this program complains that the program
spends lots of time computing the same routes over and over.  Since
their route map doesn't change very often, they want a program that
keeps track of the routes that have already been computed.  If someone
requests a route that has already been computed, the program should
return the pre-computed route.  Otherwise, it should compute the route
as before.  How should we write this program?

At first, this sounds like something we could write with an
accumulator.  This won't work though.  Why not?  An accumulator stores
information over the course of one main call to a program, but we want
to maintain information over many main calls to a program.  In other
words, we need programs with memory of how they've been called over time.

Since find-flights is a large program, let's look at the same idea on
a simpler program.  Assume we have a program f which consumes a number
and produces a number:

(define (f x)
  (g (* x x)))

Never mind what g does.  Let's just assume that it's complicated and
takes a long time to finish computing, hence we want to remember the
output of previous calls to f.  What do we need?  First, we need a
representation for previously computed values of f:

;; x-val and ans are both numbers
(define-struct result (x-arg ans))

Next, we need a way to refer to the list of previously computed
results.  We'll introduce a variable called table and set it to the
initial list of previously computed values.

(define table empty)

Now, we need to edit the definition of f to check for previously
computed values:

(define (f x)
  (local [(define prev-result (lookup x table))]
    (cond [(number? prev-result) prev-result]
	  [else (local [(define x-result (g (* x x)))]
		  ;; store result in table
		  result)])))

;; lookup : num (listof result) -> num or false
;; returns answer stored in table for arg, or false if no value stored
(define (lookup arg table)
  (local [(define prev-ans
	    (filter (lambda (res) (= arg (result-x-arg res))) table))]
    (cond [(empty? prev-ans) false]
	  [else (result-ans (first prev-ans))])))
	  
Finally, we need to change table so that it will remember this value
of f.  To do this, we need a new keyword.  It's called set!.  A set!
expression takes a variable name and an expression.  It changes the
definition of the variable to refer to the new expression.  To change
the contents of the table, we would therefore write:

     (set! table (cons (make-result x x-result) table))

Thus, the final version of f with tables would appear as follows:

(define table empty)

(define (f x)
  (local [(define prev-result (lookup x table))]
    (cond [(number? prev-result) prev-result]
	  [else (local [(define x-result (g (* x x)))]
		  (set! table (cons (make-result x x-result) table))
		  result)])))

[plus the definition of lookup from above]

This process, by which we give a program memory of which values it has
already computed, is called memoization.

There's one minor problem with this program as written: f is the only
function that uses table, so we should hide table inside of f.  How do
we hide information?  We use local.  As a first attempt, we'll try the
following:

(define (f x)
  (local [(define table empty)
	  (define prev-result (lookup x table))]
    (cond [(number? prev-result) prev-result]
	  [else (local [(define x-result (g (* x x)))]
		  (set! table (cons (make-result x x-result) table))
		  result)])))

Unfortunately, this doesn't work.  Why not?  Recall the semantics of
local.  When we evaluate (f 3), DrScheme creates a dummy variable for
the local variables table and prev-result, then uses those dummy names
in the body of the local.  So, it's as if we had written

(define dummy-table empty)
(define dummy-prev-result (lookup 3 dummy-table))

(cond [(number? dummy-prev-result) dummy-prev-result]
      [else (local [(define x-result (g (* 3 3)))]
	      (set! dummy-table (cons (make-result x x-result)
				      dummy-table))
	      result)])))

This process will create a new table with a different dummy name each
time we call f.  So clearly, this approach doesn't work.  We really
need some way to hide the table inside of f _before_ consuming a
particular value for x.  We can do this if we use lambda:

(define f
  (local [(define table empty)]
    (lambda (x)
      (local [(define prev-result (lookup x table))]
	(cond [(number? prev-result) prev-result]
	      [else (local [(define x-result (g (* x x)))]
		      (set! table (cons (make-result x x-result) table))
		      result)])))))

Why does this version work?  DrScheme still creates a dummy variable
for the table and replaces table with dummy-table inside the lambda
expression:

(define dummy-table empty)

(lambda (x)
  (local [(define prev-result (lookup x dummy-table))]
    (cond [(number? prev-result) prev-result]
	  [else (local [(define x-result (g (* x x)))]
		  (set! dummy-table (cons (make-result x x-result)
					  dummy-table))
		  result)])))

However, this creates the dummy table variable only once, then traps
it inside the lambda.  Since f refers to this lambda expression, every
time we call f, we affect the value of dummy-table.  Thus, dummy-table
persists over each call to f, which is what we wanted.

This example demonstrates something interesting about lambda
expressions: they can capture the values of variables defined outside
of their scope.  We therefore refer to lambda expressions as closures,
because they close over the values of variables defined outside of
their scope.

Let's step back and recap: what have we discussed today?

1. Sometimes, we want to equip programs with memory about the values
   that they computed previously.  This process is called memoization.

2. In order to remember values, we need a way to change what a
   variable refers to over time.  We do this with the Scheme keyword set!.

3. Use closures to create variables that will persist over several
   calls to a function.

With this summary in hand, let's return to our original problem: we
wanted to add memory to find-flights.  Let's keep just the core of the
program and modify it accordingly:

(define (find-flights start finish)
  (local [(define rm ...)
	  (define (find-helper start finish visited) ...)
	  (define (direct-cities from-city) ...)
	  (define (find-flights/list loc finish visited) ...)]
    (find-helper start finish empty)))

What do we need to add to this program?  We need a representation for
pre-computed routes, a way to store the precomputed routes, a way to
look for pre-computed routes, and a way to add newly computed routes.  
Here's the updated core (you can write lookup on your own):

;; start and finish are city and cities is (listof city)
(define-struct route (start finish cities))

(define find-flights
  (local [(existing-routes empty)]
    (lambda (start finish)
      (local [(define rm ...)
	      (define (find-helper start finish visited) ...)
	      (define (direct-cities from-city) ...)
	      (define (find-flights/list loc finish visited) ...)]
        (local [(route (lookup start finish existing-routes))]
          (cond [(cons? route) route]
		[else
		 (local [(new-route (find-helper start finish empty))]
		   (set! existing-routes (cons new-route existing-routes))
		   new-route)]))))))

As a side note, there is one annoying thing about this program: it
fixes the route map.  What if another airline wanted to use this
program?  How could you edit the program to be reusable over many
route maps, _without_ making the route map an argument to
find-flights?

Let's step back and look at how DrScheme evaluates set! expressions.
First, what is the value of a set! expression?

> (set! x 3)

is an error because you cannot set! undefined variables.  However,
what if we defined x first?

> (define x 1)
> (set! x 3)
>

Notice that DrScheme doesn't print anything.  That's because set!
expressions have no visible value.  You can think of them as having an 
invisible value, but one that you can't do anything with (since you
can't see it).

When DrScheme sees a set!, it behaves as if you changed the original
definition of the set!-ed variable.  So, if you write

	(define x 1)
	(set! x 3)

then DrScheme treats this as (define x 3) after it reaches the set!.
Here's a more complicated example:

(local [(define x 1)]
  (begin
    (set! x 3)
    x))

Begin is another new keyword.  We can use it to evaluate several
expressions in turn.  The value of the begin is the value of the last
expression in the begin.  Thus, begins only make sense if the last
statement returns a value and the remaining statements use set!.  

Let's hand-evaluate this expression.  After processing the local, we
have

(define dummy-x 1)

(begin 
  (set! dummy-x 3)
  dummy-x)

When we process the begin, we evaluate each expression in turn and
remove it from the begin.  When we process the set!, we replace the
old definition of dummy-x with the new definition as required by the
set!.  Thus, in the next step we'd get

(define dummy-x 3)

(begin
  dummy-x)

Now, we just have to evaluate (begin dummy-x), which is equivalent to
dummy-x, which is defined to be 3.