Title: OO Example Coke Machine Simulation
1OO ExampleCoke Machine Simulation
2Our Adventures Thus Far
In our last episode, we introduced the concept of
keeping state within functions.
3Each time we call (create-adder), it returns to
us a lambda body, a nameless function that we can
use or bind back to a name and hold onto. This
lambda body utilizes a variable called value
that was defined inside the local scope of the
function create-adder but outside the body of the
lambda body returned.
4Schemes Behavior
Scheme has to return a function body whenever
create-adder is called. Thats the whole purpose
of create-adder. Yet the function returned by
create-adder deals with that variable that
technically exists outside the function body, yet
not within the global scope. So Scheme decides to
create a copy of that variable and give it to
each function body to play with
5(No Transcript)
6Each of these calls to create-adder results in a
completely independent function body with its
own copy of the internal variable that it can act
upon.
7So what have we done?
- Weve managed to bind together two concepts until
this point we have had to consider as separate
items in Scheme - A sense of state (a collection of values and
variables that exist before a function is called,
that may be altered by a function during the
course of its execution and that continue to
exist after the function ceases) - Functionality that can act on a set values,
variables and parameters.
8Giving this idea a name
In Scheme, this concept is called a lexical
closure (or more simply, a closure). The idea of
closures begins to map very closely to a much
larger Computer Science concept Object
Oriented Programming. Lets talk a little bit
about Object Oriented Programming.
9Whats an Object?
You are.
Seriously. You are an object. So is the chair
youre sitting on, the screen youre looking at,
the books you lug along with you every day, the
paper you write on, the pencils you write
with. Objects, in their purest sense, originated
as an attempt to model reality. In the real
world data and functionality do not exist as
separate entities floating in space. In the real
world, data is contained within objects, and can
only be access or modified via behaviors.
10Ah.the king of caffeinated beverages. Have you
ever thought about how much money is plunked into
vending machines every day to buy these things?
Its a hefty hunk of change. So why dont we
get in on the action?
11Example
We wish to model a coke machine in Scheme, using
this idea of lexical closures and object-oriented
behavior. Well start, however, by attempting to
model our coke machines without closures, and
work our way forward from there.
12Example
Suppose we want to declare a variable that
indicates how many cans are in the
machine (define number-of-cans 5)
13Example
We then add a function that dispenses a
coke (define number-of-cans 5) (define
(get-coke) (cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))
14Example
We can now call "get-coke" five times, and on the
sixth call, we get an error message. (define
number-of-cans 5) (define (get-coke) (cond
((gt number-of-cans 0) (begin (set!
number-of-cans (- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))) We now have a function
which knows its history -- it has state.
15Testing
gt (get-coke) "have a Coke" gt (get-coke) "have a
Coke" gt (get-coke) "have a Coke" gt
(get-coke) "have a Coke" gt (get-coke) "have a
Coke" gt (get-coke) "sorry, out of Coke" gt
We can verify this works.
16Analysis
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.
We previously saw how this strategy caused
problems. In short, global variables changed by
functions are inherently problematic. Anyone
can access the data--theres no
protection. Further, how can we model MORE THAN
ONE machine? With only one global, were limited
to the number of machines we can modeland we
cant exactly rake in the money with only one
machine
17Analysis
It would be best to have a variable local to the
Coke machine.
Keeping globals for each coke machine would be
tedious, and prone to errors.
18Solution?
(define (get-coke) (let ((number-of-cans 5))
(cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))))
Since we want a variable local to each function
call, will let work?
19Failure
(define (get-coke) (let ((number-of-cans 5))
(cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))))
Since the number-of-cans variable is defined at
each invocations, every coke machine has 5 cans,
at all times. We failed to save the state of
the coke machine.
20Key Terminology
Saving State
21Analysis
We need a variable that is It's local to the
function, so nobody else can mess with it. The
state is saved in this variable, even after the
function has been exited, we call this function,
the state variable contains exactly what was left
at the last invocation. How can we do this?
22The solution
23Strategy
So, when lambda is called, Scheme takes in some
code, compiles it, and returns an executable
function.
A compiled function
(Some code)
lambda
24Strategy
What if we had the code being processed by lambda
reference a variable, perhaps defined by let?
(let ((num-cans 5))
A compiled function
(Some code)
lambda
. . . )))))
In that case, Scheme would be forced to make a
copy of the variable when it compiled the code.
25Adding Lambda
Lets convert this. We want the function below
the let to be returned by a lambda function. If
we use the part BELOW the (let ((number-of-cans
5)) statement, the function returned by lambda
wont have the code that resets the count to 5
each time. It will only have the code that uses
the variable, AFTER its been set. This means it
gets set ONCE, and not every time we call the
function.
26Testing
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
We wanted
We observed
gt (get-coke) "have a Coke"
gt (get-coke) (lambda ()...)
27Testing
What went wrong?
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
We wanted
We observed
gt (get-coke) "have a Coke"
gt (get-coke) (lambda ()...)
28Testing
Maybe it worked
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
As it turns out, this worked as it should. Its
just that Instead of returning cokes, our
function returns a function that returns cokes.
29Testing
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
Also, note the scope of the lambda return. We
purposefully left out the let, so that the
variable is KNOWN inside the lambda return, but
not reset to 5 each time the lambda function is
called.
30Testing
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
So this really worked right, and instead of data,
we get a function back? Greeeeat. How do we
use it? As it turns out, theres a function
called apply in Scheme that calls functions for
us. gt (apply (get-coke) empty) "have a Coke"
31Apply
In fact, apply can be used for many things. Its
actually just easier to invoke the function
directly.
(define (square n) ( n n))
gt square ltproceduresquaregt gt (square 3) 9 gt
(apply square '(3)) 9
32Apply
We could just apply the function we get back from
the lambda return. But theres an easier way,
perhaps. . . .
33Solution
(define (get-coke) (let ((number-of-cans 5))
(cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))))
So, lets bind a symbol to the function we got
back.
gt (define cs-machine (get-coke)) gt
(cs-machine) "have a Coke"
34Solution
So this is why its called lexical
closure. Weve created a function that is
defined as only a portion of a previous function
definition.
(define (get-coke) (let ((number-of-cans 5))
(cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))))
Only part of the function is lambda returned.
(define cs-machine (get-coke))
35Solution
Note that we do NOT include the part of the
function that resets number-of-cans back to 5.
That means when compiling the lambda, Scheme took
the value 5, and will use that quantity, without
resetting each time.
(define (get-coke) (let ((number-of-cans 5))
(cond ((gt number-of-cans 0)
(begin (set! number-of-cans
(- number-of-cans 1))
(write "have a Coke"))) (else (write
"sorry, out of Coke")))))
(define cs-machine (get-coke))
36Testing
(define (get-coke) (let ((number-of-cans 5))
(lambda () (cond ((gt number-of-cans 0)
(begin (set!
number-of-cans (-
number-of-cans 1)) (write "have a
Coke"))) (else (write "sorry, out of
Coke"))))))
gt (cs-machine) "have a Coke" gt (cs-machine) "have
a Coke" gt (cs-machine) "have a Coke" gt
(cs-machine) "have a Coke" gt (cs-machine) "sorry,
out of Coke" gt
(define cs-machine (get-coke))
37Improvements
We really ought to name the function
make-coke-machine instead of get-coke.
(define (make-coke-machine) (let
((number-of-cans 5)) (lambda () (cond
((gt number-of-cans 0) (begin
(set! number-of-cans
(- number-of-cans 1)) (write "have
a Coke"))) (else (write "sorry, out
of Coke"))))))
38Improvements
(define (make-coke-machine) (let
((number-of-cans 5)) (lambda () (cond
((gt number-of-cans 0) (begin
(set! number-of-cans
(- number-of-cans 1)) (write "have
a Coke"))) (else (write "sorry, out
of Coke"))))))
We can now make many coke machines, each with
their own inventory.
(define machine1 (make-coke-machine)) (d
efine machine2 (make-coke-machine)) (def
ine machine3 (make-coke-machine))
39Testing
gt (define cs-machine (make-coke-machine)) gt
(define ee-machine (make-coke-machine)) gt
(ee-machine) "have a Coke" gt (ee-machine) "have a
Coke" gt (ee-machine) "have a Coke" gt
(ee-machine) "have a Coke" gt (ee-machine) "have a
Coke" gt (ee-machine) "sorry, out of Coke" gt
(cs-machine) "have a Coke" gt (cs-machine) "have a
Coke" gt
We find that each machine does in fact have its
own unique copy the can number variable
40More Refinements
(define (get-coke machine) (apply machine
empty)) (define cs-machine (make-coke-machine)) (
define ee-machine (make-coke-machine)) (define
student-center-machine (make-coke-machine)) gt
(get-coke cs-machine) "have a Coke gt (get-coke
student-center-machine) have a Coke
We can now specify which machine to apply our
purchase to.
41It Doesnt Stop...
gt (define ee-machine (make-coke-machine)) gt
(get-coke ee-machine) "have a Coke" gt (get-coke
ee-machine) "have a Coke" gt (get-coke
ee-machine) "have a Coke" gt (get-coke
ee-machine) "have a Coke" gt (get-coke
ee-machine) "have a Coke" gt (get-coke
ee-machine) "sorry, out of Coke gt (define
ee-machine (make-coke-machine)) gt (get-coke
ee-machine) have a Coke
The problem we now see is that every time we want
to restock the coke machine, we have to reset the
coke machine completely.
42Solution
(define (make-coke-machine) (let
((number-of-cans 5)) (lambda () (cond
((gt number-of-cans 0) (begin
(set! number-of-cans
(- number-of-cans 1)) (write "have
a Coke"))) (else (write "sorry, out
of Coke"))))))
The problem can be solved by actually passing in
a parameter to our lambda function.
Previously, we just left it with no parameters.
43(define make-coke-machine-v2 (lambda ()
(let ((number-of-cans 0)) (lambda (dowhat)
(cond ((equal? dowhat 'load)
(begin (set! number-of-cans
20) (display number-of-cans)
(display " frosty cans waiting
for you") (newline)))
((and (equal? dowhat 'buy)
(gt number-of-cans 0)) (begin
(set! number-of-cans (-
number-of-cans 1)) (display
"have a Coke") (newline)
(display "cans remaining ")
(display number-of-cans)
(newline))) ((equal? dowhat 'buy)
(display "sorry, out of Coke"))
(else (display "please
use correct change")))))))
Our final version
44 Please exhale.
45Adding complexity
Our last example allowed us to model a very
simple coke machine. But this model is very
limited and doesnt relate to reality at all.
Lets take a look at what we created versus what
we wanted
46Old Coke Machine
- Had an internal state variable representing the
number of cokes inside the machine - Could vend a single coke at a time, subtracting a
single coke from our internal state variable - Could reset the number of cokes in the coke
machine to a constant value of 20.
47Real Coke Machines
Real coke machines have varying capacities Real
coke machines can vend a single coke at a
time Real coke machines can be reset to their
maximum capacity Real coke machines can tell a
customer that its empty before he or she
attempts to buy a coke Real coke machines hold
money. Real coke machines associate a cost with
each coke. Real coke machines can take money in
exchange for a coke Real coke machines can make
change if the customer puts in too much money.
48This could get ugly
Whew! Thats a lot of functionality for our coke
machines. Why dont we break it down into the
individual components before we create our object
out of them
49New Coke Machine The State Variables
Real coke machines have varying capacities Real
coke machines hold money. Real coke machines
associate a cost with each coke.
50New Coke Machine Initialization
We need to be able to specify the maximum number
of cans our coke machine can hold, and initialize
the number of cans we currently hold to that
value
(define (init-machine in-max) (begin (set!
MAX-CANS in-max) (set! cans MAX-CANS)))
51New Coke Machine reload
If we run out of cokes in our coke machine, we
need to be able to reload our coke machine.
Lets simplify the problem by only reloading to
our maximum capacity
(define (reload) (set! cans MAX-CANS))
52New Coke Machine is-empty?
Now we need a function that returns whether or
not a coke machine is empty or not.
(define (is-empty?) ( cans 0))
53New Coke Machine vend
Finally, something complex! Logically, we need to
be able to vend a single coke if the machine is
NOT empty. We also need to take in an idea of
cash. If the machine takes in AT LEAST the cost
of a can of coke, and if one is available, we
need to vend a coke, add money to the amount of
cash in the machine, and return the appropriate
change. If the user inserts the incorrect amount
of change, we need to handle that as well
54New Coke Machine vend
(define (vend in-money) (cond ((is-empty?)
(display "No cokes left!"))
(else (cond ((gt in-money
COST) (begin
(display "Have a Coke")
(set! cash ( cash COST))
(set! cans (- cans 1))
(- in-money COST)))
(else
(display "Add more money"))))))
55New Coke Machine how-many
For testing purposes, lets put this function in
(define (how-many) cans)
56Before we go on
Scheme is not exactly the most beautiful language
to emulate this Object Oriented behavior in. The
next function were about to see is ugly and
confusing at first. So before we lose ourselves
in those particulars, lets examine what weve
set ourselves up for
57OBJECT
Our goal is to create an object that represents a
coke machine. This object will have values
contained within it and behaviors that act on
those values.
58New Coke Machine service-manager
As we stated earlier, Object Oriented Behavior
isnt very elegant in scheme. Were stuck with
the idea of closures as one of best models.
Closures work on the idea of returning a lambda
body (nameless function) that has within it a
copy of the data we want to act on, and the
behavior we wish to enact. Our adder example
from last lecture was pretty straight-forward.
Its when we want to create multiple behaviors as
we have done that things get complicated. Lets
see how it looks and then work through some
examples
59New Coke Machine service-manager
(define (service-manager service) (cond
((symbol? service 'is-empty?) is-empty?)
((symbol? service 'how-many)
how-many) ((symbol? service
'reload) reload) ((symbol?
service 'vend)
(lambda (n) (vend n)))
(else
(display "Invalid service"))))
60make-coke-machine
(define (make-coke-machine max) (local ((define
cans 0) (define MAX-CANS 0)
(define COST .65)
(define cash 0) (define
(init-machine in-max)
(define (reload) ) (define
(is-empty?) ) (define
(how-many) ) (define (vend
in-money) ) (define
(service-manager service) ) )
))
The definitions for these functions are on the
previous slides. Lets look at what the local
actually executes
61make-coke-machine
(define (make-coke-machine max) (local
() (begin
(init-machine max)
service-manager)))
We begin by initializing our coke-machine
object. We then return the service manager
(the main functionality) as is typical in
closures.
62Lets figure out how this works
63The functionality that we returned from
make-coke-machine was this
(define (service-manager service) (cond
((symbol? service 'is-empty?) is-empty?)
((symbol? service 'how-many)
how-many) ((symbol? service
'reload) reload) ((symbol?
service 'vend)
(lambda (n) (vend n)))
(else
(display "Invalid service"))))
As we didnt provide any contracts for our
functions (shame on us!), were going to have to
play around with this function to see what
happens. It looks like this function takes in 4
different types of symbols. Lets try how-many
64(No Transcript)
65(No Transcript)
66(No Transcript)
67(No Transcript)
68(No Transcript)
69Patterns
- So it would seem that we play with objects like
this - We create an instance of our object and bind it
to a name. This instance of our object is in
fact the service-manager for our closure. - (define ltobject-namegt (ltobject creatorgt))
- We act on that service manager by passing it the
name of the functionality we want to enact - (ltobject-namegt ltname of functionalitygt)
- This returns to us the actual functionality
itself, which we can then pass parameters to and
use - ((ltobject-namegt ltname of functionalitygt)
ltparametergt)
70A View of Memory
71Two Views
- External
- (define fred
- (make-coke-machine 'fred 3))
- ((fred 'vend))
- ((fred 'load) 10)
Internal (define (make-coke-machine ) (local
((define cans ) (define init-machine )
(define vend ) (define load )
(define service-manager)) (begin
(constructor ) service-manager))))
72What happens?
Heap
Stack
- (define fred
- (make-coke-machine ))
Inter- actions
73What happens?
fred is defined
Heap
Stack
fred
- (define fred
- (make-coke-machine ))
Inter- actions
74What happens?
fred is defined coke-machine is created
coke-machine cans (vend) (load)
Heap
3
Stack
fred
- (define fred
- (make-coke-machine ))
Inter- actions
75What happens?
coke-machine cans (vend) (load)
Heap
2
Stack
fred
Inter- actions
Have a Coke!
76What happens?
coke-machine cans (vend) (load)
Heap
1
Stack
fred
- ((fred 'vend))
- ((fred 'vend))
Inter- actions
Have a Coke! Have a Coke!
77What happens?
coke-machine cans (vend) (load)
Heap
0
Stack
fred
- ((fred 'vend))
- ((fred 'vend))
- ((fred 'vend))
Inter- actions
Have a Coke! Have a Coke! Have a Coke!
78What happens?
coke-machine cans (vend) (load)
Heap
0
Stack
fred
- ((fred 'vend))
- ((fred 'vend))
- ((fred 'vend))
Inter- actions
Have a Coke! Have a Coke! Sorry!
79 coke-machine-ab,c.scm
80Adding Flavor to the Coke Machine
- Adding flavors
- vectors
- modifying methods
- vend
- load
81Final Thought
(local ( ) service-manager)
declarations
initialization
methods
82Questions?
83(No Transcript)