MIT OpenCourseWare
http://ocw.mit.edu


6.001 Structure and Interpretation of Computer Programs, Spring 2005

Transcript – 5A: Assignment, State, and Side-effects



[MUSIC PLAYING] PROFESSOR: Well, so far we've invented enough programming to do
some very complicated things. And you surely learned a lot about programming at this

point. You've learned almost all the most important tricks that usually don't get taught to
people until they have had a lot of experience. For example, data directed programming is a

major trick, and yesterday you also saw an interpreted language.




We did this all in a computer language, at this point, where there was no assignment

statement. And presumably, for those of you who've seen your Basic or Pascal or whatever,
that's usually considered the most important thing. Well today, we're going to do some

thing horrible. We're going to add an assignment statement.




And since we can do all these wonderful things without it, why should we add it? An

important thing to understand is that today we're going to, first of all, have a rule, which is
going to always be obeyed, which is the only reason we ever add a feature to our language

is because there is a good reason. And the good reason is going to boil down to the ability,
you now get an ability to break a problem into pieces that are different sets of pieces then

you could have broken it down without that, give you another means of decomposition.




However, let's just start. Let me quick begin by reviewing the kind of language that we have

now. We've been writing what's called functional programs. And functional programs are a
kind of encoding of mathematical truths. For example, when we look at the factorial

procedure that you see on the slide here, it's basically two clauses. If n is one, the result is
one, otherwise n times factorial n minus one. That's factorial of n. Well, that is factorial of n.





And written down in some other obscure notation that you might have learned in calculus
classes, mathematical logic, what you see there is if n equals one, for the result of n

factorial is one, otherwise, greater than one, n factorial is n times n minus one factorial.




True statements, that's the kind of language we've been using. And whenever we have true

statements of that sort, there is a kind of, a way of understanding how they work which is
that such processes can be involved by substitution.
And so we see on the second slide here, that the way we understand the execution implied

by those statements in arranged in that order, is that you do successive substitutions of
arguments for formal parameters in the body of a procedure.





This is basically a sequence of equalities. Factorial four is four times factorial three. That is
four times three times factorial of two and so on. We're always preserving truth. Even

though we're talking about true statements, there might be more than one organization of
these true statements to describe the computation of a particular function, the computation

of the value of a particular function.




So, for example, looking at the next one here. Here is a way of looking at the sum of n and

m. And we did this one by a recursive process. It's the increment of the sum of the
decrement of n and m. And, of course, there is some piece of mathematical logic here that

describes that. It's the increment of the sum of the decrement of n and m, just like that. So
there's nothing particularly magic about that.





And, of course, if we can also look at an iterative process for the same, a program that
evolves an iterative process, for the same function. These are two things that compute the

same answer. And we have equivalent mathematical truths that are arranged there. And
just the way you arrange those truths determine the particular process. In the way choose

and arrange them determines the process that's evolved.




So we have the flexibility of talking about both the function to be computed, and the

method by which it's computed. So it's not clear we need more. However, today I'm going
to this awful thing. I'm going to introduce this assignment operation.





Now, what is this? Well, first of all, there is going to be another kind of kind of statement, if
you will, in a programming language called Set! Things that do things like assignment, I'm

going to put exclamation points after. We'll talk about what that means in a second. The
exclamation point, again like question mark, is an arbitrary thing we attach to the symbol

which is the name, has no significance to the system. The only significance is to me and you
to alert you that this is an assignment of some sort.





But we're going to set a variable to a value. And what that's going to mean is that there is a
time at which something happens. Here's a time. If I have time going this way, it's a time

access. Time progresses by walking down the page. Then an assignment is the first thing
we have that produces the difference between a before and an after.
All the other programs that we've written, that have no assignments in them, the order in
which they were evaluated didn't matter. But assignment is special, it produces a moment

in time. So there is a moment before the set occurs and after, such that after this moment
in time, the variable has the value, value. Independent of what value it had before, set!

changes the value of the variable.




Until this moment, we had nothing that changed. So, for example, one of the things we can

think of is that the procedures we write for something like factorial are in fact pretty much
identical to the function factorial. Factorial of four, if I write fact4, independent of what

context it's in, and independent of how many times I write it, I always get the same answer.

It's always 24. It's a unique map from the argument to the answer.




And all the programs we've written so far are like that. However, once I have assignment,
that isn't true. So, for example, if I were to define count to be one. And then I'm going to

define also a procedure, a simple procedure called demo, which takes argument x and does

the following operations. It first sets x to x plus one. My gosh, this looksjust like FORTRAN,
right-- in a funny syntax. And then add to x count, Oh, I just made a mistake.




I want to say, set! count to one plus count. It's this thing defined here. And then plus x

count. Then I can try this procedure. Let's run it. So, suppose I get a prompt and I say,

demo three.




Well, what happens here? The first thing that happens is count is currently one. Currently,
there is a time. We're talking about time. x gets three. At this moment, I say, oh yes, count

is incremented, so count is two. two plus three is five. So the answer I get out is five. Then

I say, demo of say, three again.




What do I get? Well, now count is two, it's not one anymore, because I have incremented it.
But now I go through this process, three goes into x, count becomes one plus count, so

that's three now. The sum of those two is six, so the answer is six.




And what we see is the same expression leads to two different answers, depending upon

time. So demo is not a function, does not compute a mathematical function. In fact, you
could also see why now, of course, this is the first place where the substitution model isn't

going to work. This kills the substitution model dead.
You know, with quotation there were some little problems that a philosopher might notice
with the substitutions, because you have to worry about what deductions you can make

when you substitute into quotes, if you're allowed to do that at all. But here the substitution
model is dead, can't do anything at all.





Because, supposing I wanted to use a substitution model to consider substituting for count?
Well, my gosh, if I substitute for here and here, they're different ones. It's not the same

count any more. I get the wrong answer. The substitution model is a static phenomenon
that describes things that are true and not things that change. Here, we have truths that

change.




OK, Well, before I give you any understanding of this, this is very bad. Now, we've lost our

model of computation. Pretty soon, I'm going to have to build you a new model of
computation. But ours plays with this, just now, in an informal sense. Of course, what you

already see is that when I have something like assignment, the model that we're going to

need is different from the model that we had before in that the variables, thosesymbols like
count, or x are no longer going to refer to the values they have, but rather to some sort of

place where the value restored.




We're going to have to think that way for a while. And it's going to be a very bad thing and

cause a lot of trouble. And so, as I said, the very fact that we're inventing this bad thing,
means that there had better be a good reason for it, otherwise, just a waste of time and a

lot of effort.




Let's just look at some of it just to play. Supposing we write down the functional version,

functional meaning in the old style, of factorial by an iterative process. Factorial of n, we're
going to iterate of m and i, which says if i is greater than n, then the result is m, otherwise,

the result of iterating the product of i and m. So m is going to be the product that I'm
accumulating. m is the product. And the count I'm going to increase by one. Plus, ITER,

ELSE, COND, define.




I'm going to start this up. And these days, you should have no trouble reading something

like this. What I have here is a product there being accumulated and a counter. I start them
up both at one. I'm going to buzz the counter up, i goes to i plus one every time around.

But that's only our putting a time on the process, each of this is just a set of truths, true
rules. And m is going to get a new values of i and m, i times m each time around, and

eventually i is going to be bigger than n, in which case, the answer's going to be m.
Now, I'm speaking to you, use time in this. That's just because I know how the computer
works. But I didn't have to. This could be a purely mathematical description at this point,

because substitution will work for this.




But let's set right down a similar sort of program, using the same algorithm, but with

assignments. So this is called the functional version. I want to write down an imperative
version. Factorial of n. I'm going to create my two variables. Let i initialize itse lf to one, and

m be initialized to one, similar.




We'll create a loop which has COND greater than i, and if i is greater than n, we're done.

And the result is m, the product I'm accumulating. Otherwise, I'm going to write down three
things to do. I'm going to set! m to the product of i and m, set! i to the sum of i and one,

and go around the loop again. Looks very familiar to you FORTRAN programmers. ELSE,
COND, define, funny syntax though. Start the loop up, and that's the program.





Now, this program, how do we think about it? Well, let's just say what we're seeing here.
There are two local variables, i and m, that have been initialized to one. Every time around

the loop, I test to see if i is greater than n, which is the input argument, and if so, the re sult
is the product being accumulated in m. However, if it's not the end of the loop, if I'm not

done, then what I'm going to do is change the product to be the result of multiplying i times

the current product.




Which is sort of what we were doing here. Except here I wasn't changing. I was making
another copy, because the substitution model says, you copy the body of the procedure with

the arguments substituted for the formal parameters. Here I'm not worried about copying,

here I've changed the value of m. I also then change the value of i to i plus one, and go
buzzing around.




Seems like essentially the same program, but there are some ways of making errors here

that didn't exist until today. For example, if I were to do the horrible thing of not being
careful in writing my program and interchange those two assignments, the program

wouldn't compute the same function.




I get a timing error because there's a dependency that m depends upon having the last

value of i. If I try to i first, then I've got the wrong value of i when I multiply by m. It's a
bug that wasn't available until this moment, until we introduced something that had time in

it.




So, as I said, first we need a new model of computation, and second, we have to be damn

good reason for doing this kind of ugly thing. Are there any questions? Speak loudly, David.




AUDIENCE: I'm confused about, we've introduced set now, but we had let before and define
before. I'm confused about the difference between the three. Wouldn't define work in the

same situation as set if you introduced it a bit?




PROFESSOR: No, define is intended for setting something once the first time, for making it.

You've never seen me write on a blackboard two defines in a row whose intention was to
change the old value of some variable to a new one.





AUDIENCE: Is that by convention or--




PROFESSOR: No, it's intention. The answer is that, for example, internal to a procedure, two

defines in a row are illegal, two defines in a row of the same variable. x can't be defined
twice. Whether or not a system catches that error is a different question, but I legislate to

you that define happens once on anything.




Now, indeed, in interactive debugging, we intend that you interacting with your computer

will redefine things, and so there's a special exception made for interactive debugging. But
define is intended to mean to set up something which will be forever that value after that

point. It's as if all the defines were done at the beginning. In fact, the only legal place to put
a define in Scheme, internal to a procedure, is just at the beginning of a lambda expression,

the beginning of the body of a procedure.




Now, let of course does nothing like either of that. I mean, if you look at what's happening

with a let, this happens again exactly once. It sets up a context where i and m are values
one and one. That context exists throughout this scope, this region of t he program.

However, you don't think of that let as setting i again. It doesn't change it. i never changes
because of the let. i gets created because of let.
In fact, the let is a very simple idea. Let does nothing more, Let a variable one to have

value one; I'll write this down a little bit more neatly; Let's write, var one have value, the
value of expression e1, and variable two, have this value of the expression e2, in an

expression e3, is the same thing as a procedure of var one and var two, the formal
parameters, and e3 being the body, where var one is bound to the value of e1, and var two

gets the value of e2.




So this is, in fact, a perfectly understandable thing from a substitution point of view. This is

really the same expression written in two different ways. In fact, the way the actual system
works is this gets translated into this before anything happens.






AUDIENCE: OK, I'm still unclear as then what makes the difference between a let and a

define. They could--




PROFESSOR: A define is a syntactic sugar, whereby, essentially a bunch of variables get

created by lets and then set up once. OK, time for the first break, I think. Thank you.
[MUSIC PLAYING]





Well let's see. I now have to rebuild the model of computation, so you understand how
some such mechanical mechanism could work that can do what we've just talked about. I

just recently destroyed your substitution model. Unfortunately, this model is significantly
more complicated than the substitution model.





It's called the environment model. And I'm going to have to introduce some terminology,
which is very good terminology for you to know anyway. It's about names. And we're going

to give names to the kinds of names things have and the way those names are used. So
this is a meta-description, if you will. Anyway, there is a pile of an unfortunate terminology

here, but we're going to need this to understand what's called the environment model.
We're about to do a little bit of boring, dog-work here.





Let's look at the first transparency. And we see a description of a word called bound. And
we're going to say that a variable, v, is bound in an expression, e, if the meaning of e is

unchanged by the uniform replacement of a variable w, not occurring in e, for every
occurrence of v in e. Now that's a long sentence, so, I think, I'm going to have to say a little

bit about that before we even fool around at all here.
Bound variables we're talking about here. And you've seen lots of them. You may not know

that you've seen lots of them. Well, I suppose in your logic you saw a logical variables like,
for every x there exists a y such that p is true of x and y from your calculus class. This

variable, x, and this variable, y, are bound, because the meaning of this expression does
not depend upon the particular letters I used to describe x and y. If I were to change the w

for x, then said for every w there exists a y such that p is true of w and y, it would be the
same sentence. That's what it means.





Or another case of this that you've seen is integral say, from 0 to one of dx over one plus x
square. Well that's something you see all the time. And this x is a bound variable. If I

change that to a t, the expression is still the same thing. This is a 1/4 of the arctan of one
or something like that. Yes, that's the arctan of one. So bound variables are actually fairly

common, for those of you who have played a bit with mathematics.




Well, let's go into the programming world. Instead of the quantifier being something like, for

every, or there exists, or integral, a quantifier is a symbol that binds a variable. And we are
going to use the quantifier lambda as being the essential thing that binds variables.





And so we have some nice examples here like that procedure of one argument y which does
the following thin g. It calls the procedure of one argument x, which multiplies x by y, and

applies that to three. That procedure has the property there of two bound variables in it, x
and y. This quantifier, lambda here, binds this y, and this quantifier, lambda, binds tha t x.

Because, if I were to take an arbitrary symbol does not occur in this expression like w and

replace all y's with w's in this expression, the expression is still the same, the same
procedure.




And this is an important idea. The reason why we had such things like that is a kind of

modularity. If two people are writing programs, and they work together, it shouldn't matter

what names they use internal to their own little machines that they're building. And so,
what I'm really telling you there, is that, for example, this is equivalent to that procedure of

one argument y which uses that procedure of one argument d which multiplies z by y.
Because nobody cares what I used in here. It's a nice example.




On the other hand, I have some variables that are not b ound. For example, that procedure

of one argument x which multiplies x by y. In this case, y is not bound. Supposing y had the

value three, and z had the value four, then this procedure would be the thing that multiplies
its argument by three. If I were to replace every instance of y with z, I would have a

different procedure which multiplies every argument that's given by four.
And, in fact, we have a name for such a variable. Here, we say that a variable, v, is free in
the expression, e, if the meaning of the expression, e, is changed by the uniform

replacement of a variable, w, not occurring in e for every occurrence of v and e.




So that's why this variable over here, y, is a free variable. And so free variables in this

expression-- And other examples of that is that procedure of one argument y, which is just
what we had before, which uses that procedure of one argument x that multiplies x by y--

use that on three. This procedure has a free variable in it which is asterisk. See, because, if
that has a normal meaning of multiplication, then if I were to replace uniformly all asterisks

with pluses, then the meaning of this expression would change. That's what you mean by a

free variable.




So, so far you've learned some logician words which describe the way names are used.
Now, we have to do a little bit more playing around here, a little bit more. I want to tell you

about the regions are over which variables are defined.




You see, we've been very informal about this up till now, and, of course, many of you have

probably understood very clearly or most of you, that the x that's being declared here is
defined only in here. This x is the defined only in here, and this y is defined only in here. We

have a name for such an idea. It's called a scope.




And let me give you another piece of terminology. It's a long story. If x is a bound variable

in e, then there is a lambda expression where it is bound. So the only way you can get a
bound variable ultimately is by lambda expression. Then you may worry, does define quite

an exception to this? And it turns out, we could always arrange things so you don't need

any defines. And we'll see that in a while. It's a very magical thing. So define really can go
away. The really, only thing that makes names is lambda . That's its job. And what's so

amazing about a lot of things is you can compute with only lambda.




But, in any case, a lambda expression has a place where it declares a variable. We call it the
formal parameter list or the bound variable list. We say that the lambda expression binds --

so it's a verb-- binds the variables declared in it's found variable list. In addition, those

parts of the expression where the variable is defined, which was declared by some
declaration, is called the scope of that variable. So these are scopes. This is the scope of y.

And this is the scope of x-- that sort of thing.
OK, well, now we have enough terminology to begin to understand how to make a new

model for computation, because the key thing going on here is that we destroyed the
substitution model, and we now have to have a model that represents the names as

referring to places. Because if we are going to change something, then we have a place
where it's stored.





You see, if a name only refers to a value, and if I tried to change the name's meaning, well,
that's not clear. There's nothing that is the place that that name referred to. How am I

really saying it? There is nothing shared among all of the instances of that name. And what
we really mean, by a name, is that we fan something out. We've given something a name,

and you have it, and you have it, because I'm given you a reference to it, and I've give n
you a reference to it. And we'll see a lot about that.





So let me tell you about environments. I need the overhead projection machine, thank you.
And so here is a bunch of environment structures. An environment is a way of doing

substitutions virtually. It represents a place where something is stored which is the
substitutions that you haven't done. It's a place where everything accumulates, where the

names of the variables are associated with the values they have such that when you say,

what dose this name mean, you look it up in an environment.




So an environment is a function, or a table, or something like that. But it's a structured sort
of table. It's made out of things called frames. Frames are pieces of environment, and they

are chained together, in some nice ways, by what's called parent links or something like

that.




So here, we have an environment structure consisting of three environments, basically, a,
b, and c. d is also an environment, but it's the same one, they share. And that's the essen ce

of assignment. If I change a variable, a value of a valuable that lives here, like that one, it

should be visible from all places that you're looking at it from. Take this one, x. If I change
the x to four, it's visible from other places. But I'm not going to worry about that right now.

We're going to talk a lot about that in a little while.




What do we have here? Well, these are called frames. Here is a frame, here's a frame, and
here's a frame. a is an environment which consists of the table which is frame two, followed

by the table labeled frame one. And, in this environment, in say this environment, frame

two, x and y are bound. They have values. Sorry, in frame one -- In frame two, z is bound,
and x is bound, and y is bound, but the value of x that we see, looking from this point of

view, is this x. It's x is seven, rather than this one which is three. We say that this x
shadows this x. From environment three-- from frame three, from environment b, which
refers to frame three, we have variables n and y bound and also x. This y shadow this one.

So the value, looking from this point of view, of y is two. The value for looking from this
point of view and m is one. And the value, looking from this point of view, of x is three.





So there we have a very simple environment structure made out of frames. These
correspond to the applications of procedures. And we'll see that in a second. So now I have

to make you some other nice little structure that we build.




Next slide, we see an object, which I'm going to draw procedures. This is a procedure. A

procedure is made out of two parts. It's sort of like a cons. However, it's the two parts. The
first part refers to some code, something that can be executed, a set of instructions, if you

will. You can think of it that way. And the second part is the environment. The procedure is
the whole thing. And we're going to have to use this to capture the values of the free

variables that occur in the procedure. If a variable occurs in the procedure it's either bound
in that procedure or free. If it's bound, then the value will somehow be easy to find. It will

be in some easy environment to get at. If it's free, we're going to have to have something
that goes with the procedure that says where we'll go look for its value. And the reasons

why are not obvious yet, but will be soon.




So here's a procedure object. It's a composite object consisting of a piece of code and a

environment structure. Now I will tell you the new rules, the complete new rules, for
evaluation. The first rule is-- there's only two of them. These correspond to the substitution

model rules. And the first one has to do with how do you apply a procedure to its

arguments? And a procedural object is applied to a set of arguments by constructing a new
frame. That frame will contain the mapping of the former parameters to the actual

parameters of the arguments that were supplied in the call.




As you know, when we make up a call to a procedure like lambda x times x y, and we call

that with the argument three, then we're going to need some mapping of x to three. It's the
same thing as later substituting, if you will, the three for the x in the old model. So I'm

going to build a frame which contains x equals three as the information in that frame.




Now, the body of the procedure will then have to be evaluated which is this. I will be
evaluated in an environment which is constructed by adjoining the new frame that we just

made to the environment which was part of the procedure that we applied.
So I'm going to make a little example of that here. Supposing I have some environ ment.

Here's a frame which represents it. And some procedure-- which I'm going to draw with
circles here because it's easier than little triangles-- Sorry, those are rhombuses,

rhomboidal little pieces of fruit jelly or something.




So here's a procedure which takes this environment. And the procedure has a piece of code,

which is a lambda expression, which binds x and y and then executes an expression, e. And
this is the procedure. We'll call it p. I wish to apply that procedure to three and four. So I

want to do p of three and four.




What I'm going to do, of course, is make a new frame. I build a frame which contains x

equals three, and y equals four. I'm going to connect that frame to this frame over here.
And then this environment, with I will call b, is the environment in which I will evaluate the

body of e. Now, e may contain references to x and y and other things. x and y will have
values right here. Other things will have their values here.





How do we get this frame? That we do by the construction of procedures which is the other
rule. And I think that's the next slide. Rule two, when a lambda expression is evaluated,

relative to a particular environment-- See, the way I get a procedure is by evaluating the
lambda expression.





Here's a lambda expression. By evaluating it, I get a procedure which I can apply to three.
Now this lambda expression is evaluated in an environment where y is defined. And I want

the body of this which contains a free version of y. y is free in here, it's bound over the
whole thing, but it's free over here. I want that y to be this one. I evaluate this body of this

procedure in the environment where y was created. That's this kind of thing, because that
was done by application.





Now, if I ever want to look up the value of y, I have to know where it is. Therefore, this
procedural was created, the creation of the procedure which is the result of evaluating that

lambda expression had better capture a pointer or remember the frame in which y was
bound. So that's what this rule is telling us.





So, for example, if I happen to be evaluating a lambda expression, lambda expression in e,
lambda of say, x and y, let's call it g in e, evaluating that. Well, all that means is I now

construct a procedure object. e is some environment. e issomething which has a pointer to
it. I construct a procedure object that points up to that environment, where the code of that

is a lambda expression or whatever that translates into. And this is the procedure.




So this produces for me-- this object here, this environment pointer, captures the place

where this lambda expression was evaluated, where the definition was used, where the
definition was used to make a procedure, to make the procedure. So it picks up the

environment from the place where that procedure was defined, stores it in the procedure
itself, and then when the procedure is used, the environment where it was defined is

extended with the new frame.




So this gives us a locus for putting where a variable has a value. And, for example, if there

are lots of guys pointing in at that environment, then they share that place. And we'll see
more of that shortly.





Well, now you have a new model for understanding the execution of programs. I suppose I'll
take questions now, and then we'll go on and use that for something.




AUDIENCE: Is it right to say then, the environment is that linked chain of frames --





PROFESSOR: That's right.




AUDIENCE: starting with-- working all the way back?




PROFESSOR: Yes, the environment is a sequence of frames linked together. And the way I

like to think about it, it's the pointer to the first one, because once you've got that you've

got them all. Anybody else?




AUDIENCE: Is it possible to evaluate a procedure or to define a procedure in two different
environments such that it will behave differently, and have pointers to both --





PROFESSOR: Oh, yes. The same procedure is not going to have two different environments.
The same code, the same lambda expression can be evaluated in two environments

producing two different procedures. Each procedure--
AUDIENCE: Their definition has the same name. Their operation--




PROFESSOR: The definition is written the same, with the same characters. I can evaluate

that set of characters, whatever, that list structure that defines, that is the textual
representation. I can evaluate that in two different environments producing two different

procedures. Each of those procedures has its own local sets of variables, and we'll see that
right now. Anybody else? OK, thank you. Let's take a break. [MUSIC PLAYING]





Well, now I've done this terrible thing to you. I've introduced a very complicated thing,
assignment, which destroys most of the interesting mathematical properties of our

programs. Why should I have done this? What possible good could this do? Clearly not a
nice thing, so I better have a good excuse. Well, let's do a little bit of playing, first of all,

with some very interesting programs that have assignment. Understand s omething special
about them that makes them somewhat valuable.





Start with a very simple program which I'm going to call make-counter. I'm going to define
make-counter to be a procedure of one argument n which returns as its value a procedure

of no arguments-- a procedure that produces a procedure-- which sets n to the increment of
n and returns that value of n.





Now we're going to investigate the behavior of this. It's a sort of interesting thing. In order
to investigate the behavior, I have to make an environment model, because we can't

understand this any other way. So let's just do that.




We start out with some sort of-- let's say there is a global environment that the machine is

born with. Global we'll call it. And it's going to have in it a bunch of initial things. We all
know what it's got. It's got things in it like say, plus, and times, and quotient, and

difference, and CAR, and et cetera, lots of things. I don't know what they are, some various
squiggles that are the things the machine is born with. And by doing the definition here,

what I plan to do--




Well, what am I doing? I'm doing this relative to the global environment. So here's my

environment pointer. In order to do that I have to evaluate this lambda expression. That
means I make a procedure object.
So I'm going to make a procedure object here. And the procedure object has, as the place

it's defined, the global environment. The procedure object contains some code that
represents a procedure of one argument n which returns a procedure ofno arguments

which does something. And the define is a way of changing this environment, so that I now
add to it a make-counter, a special rule for the special thing defined. But what that is, is it

gives me that pointer to that procedure. So now the global environment contains make-
counter as well.





Now, we're going to do some operations. I'm going to use this to make some counters. We'll
see what a counter is. So let's define c1 to be a counter beginning at 0. Well, we know how

to do this now, according to the model. I have to evaluate the expression make-counter in
the global environment, make-counter of 0.





Well, I look up make-counter and see that it's a procedure. I'm going to have to apply that
procedure. The way I apply the procedure is by constructing a frame. So I construct a frame

which has a value for n in it which is 0, and the parent environment is the one which is the
environment of definition of make-counter. So I've made an environment by applying make-

counter to 0.




Now, I have to evaluate the body of make-counter, which is this lambda expression, in that

environment. Well evaluating this body, this body is a lambda expression. Evaluate a
lambda expression means make a procedure object. So I'm going to make a procedure

object.




And that procedure object has the environment it was defined in being that, where n was

defined to be 0. And it has some code, which is the procedure of no arguments which does
something, that sets something, and returns n. And this thing is going to be the object,

which in the global environment, will have the name c1. So we construct a name here, c1,

and say that equals that.




Now, but also make another counter, c2 to be make-counter say, starting with 10. Then I
do essentially the same thing. I apply the make-counter procedure, which I got from here,

to make another frame with n being 10. That frame has the global environment as its
parent. I then construct a procedure which has that as it's frame of definition. The code of it

is the procedure of no arguments which does something. And it does a set, and so on. And

n comes out. And c2 is this.
Well, you're already beginning to see something fairly interesting. There are two n's here.

They are not one n. Each time I called make-counter, I made another instance of n. These
are distinct and separate from each other. Now, let's do some execution, use those

counters. I'm going to use those counters.




Well, what happens if I say, c1 at this point? Well, I go over here, and I say, oh yes, c1 is a

procedure. I'm going to call this procedure on no arguments, but it has no parameters.
That's right. What's its body? Well, I have to look over here, because I didn't write it down.

It said, set n to one plus n and return n, increment n.




Well, the n it sees is this one. So I increment that n. That becomes one, and I return the

value one. Supposing I then called c2. Well, what do I do? I say c2 is this procedure which
does the same thing, but here's the n. It becomes 11. And so I have an 11 which is the

value. I then can say, let's try c1 again. c1 is this, that's two, so the answer is two. And c2
gives me a 12 by the same method, by walking down here looking at that and saying,

here's the n, I'm incrementing.




So what I have are computational objects. There are two counters, each with its own

independent local state. Let's talk about this a little. This is a strange thing. What's an
object? It's not at all obvious what an object is. We like to think about objects, because it's

economical to think that way. It's an intellectual economy. I am an object. You are an
object. We are not the same object.





I can divide the world into two parts, me and you, and there's other things as well, such
that most of the things I might want to discuss about my workings do not involve you, and

most of the things I want to discuss about your workings don't involve me. I have a blood
pressure, a temperature, a respiration rate, a certain amount of sugar in my blood, and

numerous, thousands, of state variables-- millions actually, or I don't know how many--

huge numbers of state variables in the physical sense which represent the state of me as a
particle, and you have gazillions of them as well.




And most of mine are uncoupled to most of yours. So we can compute the properties of me

without worrying too much about the properties of you. If we had to work about both of us
together, than the number of states that we have to consider is the product of the number

of states you have and the number of states I have. But this way it's almost a sum.
Now, indeed there are forces that couple us. I'm talking to you and your state changes. I'm

looking at you and my state changes. Some of my state variables, a very few of them,
therefore, are coupled to yours. If you were to suddenly yell very loud, my blood pressure

would go up.




However, and it may not be always appropriate to think about the world as being made out

of independent states and independent particles. Lots of the bugs that occur in things like
quantum mechanics, or the bugs in our minds that occur when we think about things like

quantum mechanics, are due the fact that we are trying to think about things b eing broken
up into independent pieces, when in fact there's more coupling than we see on the surface,

or that we want to believe in, because we want to compute efficiently and effectively. We've
been trained to think that way.





Well, let's see. How would we know if we had objects at all? How can we tell if we have
objects? Consider some possible optical illusions. This could be done. These pieces of chalk

are not appropriately identical, but supposing you couldn't tell the difference of them by
looking at them. Well, there's a possibility that this all a game I'm playing with mirrors. It's

really the same piece of chalk, but you're seeing two of them. How would you know if you're

seeing one or two? Well, there's only one way I know. You grab one of them and change it
and see if the other one changed. And it didn't, so there's two of them.




And, on the other hand, there is some other screwy properties of things like that. Like, how

do we know if something changed? We have to look at it before and after the change. The

change is an assignment, it's a moment in time. But that means we have to know it was
the same one that we're looking at. So some very strange, and unusual, and obscure, and --

I don't understand the problems associated with assignment, and change, and objects.
These could get very, very bad.





For example, here I am, I am a particular person, a particular object. Now, I can take out
my knife, and cut my fingernail. A piece of my fingernail has fallen off onto the table. I

believe I am the same person I was a second ago, but I'm not physically the same in the
slightest.




I have changed. Why am I the same? What is the identity of me? I don't know. Except for

the fact that I have some sort of identity. And so, I think by introducing assignment and

objects, we have opened ourselves up to all the horrible questions of philosophy that have
been plaguing philosophers for some thousands of years about this sort of thing. It's why

mathematics is a lot cleaner.
Let's look at the best things I know to say about actions and identity. We say that an action,
a, had an effect on an object, x, or equivalently, that x was changed by a, if some property,

p, which was true of x before a, became false of x after a. Let's test. It still means I have to
have the x before and after. Or, the other way of saying this is, we say that two objects x

and y are the same for any action which has an effect on x has the same effect on y.




However, objects are very useful, as I said, for intellectual economy. One of the things

that's incredibly useful about them, is that the world is, we like to think about, made out of
independent objects with independent local state. We like to think that way, although it isn't

completely true.




When we want to make very complicated programs that deal with such a world, if we want

those programs to be understandable by us and also to be changeable, so that if we change
the world we change the program only a little bit, then we want there to be connections,

isomorphism, between the objects in the world and the objects in our mental model. The

modularity of the world can give us the modularity in our programming. So we invent things
called object-oriented programming and things like that to provide us with that power.




But it's even easier. Let's play a little game. I want to play a little game,show you an even

easier example of where modularity can be enhanced by using an assignment statement,

judiciously. One thing I want to enforce and impress on you, is don't use assignment
statements the way you use it in FORTRAN or Basic or something or Pascal, to do the things

you don't have to do with it. It's not the right way to think for most things. Sometimes it's
essential, or maybe it's essential. We'll see more about that too.





OK, let me show you a fun game here. There was mathematician by the nam e of Cesaro--
or Cesaro, Cesaro I suppose it is-- who figured out a clever way of computing pi. It turns

out that if I take to random numbers, two integers at random, and compute the greatest
common divisor, their greatest common divisor is either one or it's not one. If it's one, then

they have no common divisors. If their greatest common divisor is one-- the probability that
two random numbers, two numbers chosen at random, has as greatest common divisor one

is related to pi.




In fact-- yes, it's very strange-- of course there are other ways of computing pi, like

dropping pins on flags, and things like that, and sort of the same kind of thing. So the
probability of that the GCD of number one and number two, two random numbers chosen, is
6 over pi squared. I'm not going to try to prove that. It's actually not too hard and sort of

fun.




How would we estimate such probability? Well, the way we do that, the way we estimate

probabilities, is by doing lots of experiments, and then computing the ratios of the ones that
come out one way to the total number of experiments we do. It's called Monte Carlo, and

it's useful in other contexts for doing things like integrals where you have lots and lots of
variables-- the space which is limiting the dimensions you are do ing you integral in.





But going back to here, Let's look at this slide, We can use Cesaro's method for estimating
pi with n trials by taking the square root of six over a Monte Carlo, a Monte Carlo

experiment with n trials, using Cesaro's experiment, where Cesaro's experiment is the test
of whether the GCD of two random numbers-- And you can see that I've already got some

assignments in here, just by what I wrote. The fact that this word rand, in parentheses,
therefore, that procedure call, yields a different value than this one, at least that's what I'm

assuming by writing this this way, indicates that this is not a function, that there's internal
state in it which is changing. If the GCD of those two random numbers is equal to one,

that's the experiment.




So here I have an experimental method for estimating the value of pi. Where, I can easily

divide this problem into two parts. One is the specific Monte Carlo experiment of Cesaro,
which you just saw, and the other is the general technique of doing Monte Carlo

experiments. And that's what this is.




If I want to do Monte Carlo experiments with n trials, a certain number of trials, and a

particular experiment, the way I do that is I make a little iterative procedure which has
variable the number of trials remaining and the number trials that have been passed, that

I've gotten true. And if the number remaining is 0, then the answer is the number past

divided by this whole number of trials, was the estimate of the probability. And if it's not, if
I have more trials to do, then let's do one.




We do an experiment. We call the procedure which is experiment on no arguments. We do

the experiment and then, if that turned out to be true, we go around the loop decrementing
the number of experiments we have to do by one and incrementing the number that were

passed.
And if the experiment was false, we just go around the loop decrementing the number of

experiments remaining and keeping the number passed the same. We start this up iterating
over the total number of trials with 0 experiments past. A very elegant little program. And I

don't have to just do this with Cesaro's experiment, it could be lots of Monte Carlo
experiments I might do.





Of course, this depends upon the existence of some sort of random number generator. And
random number generators generally look something like this. There is a random number

generator-- is in fact a procedure which is going to do something just like the counter. It's
going to update an x to the result of applying some function to x, where this function is

some screwy kind of function that you might find out in Knuth's books on the details of
programming. He does these wonderful books that are full of the details of programming,

because I can't remember how to make a random number generator, but I can look it up

there, and I can find out.




And then, eventually, I return the value of x which is the state variable internal to the
random number generator. That state variable is initialized somehow, and has a value. And

this procedure is defined in the context where that variable is bound. So this is a hidden

piece of local state that you see here. And this procedure is defined in that context.




Now, that's a very simple thing to do. And it's very nice. Supposing, I didn't want to use
assignments. Supposing, I wanted to write this program without assignments. What

problems would I have? Well, let's see. I'd like to use the overhead machine here, thank

you.




First of all, let's look at the whole thing. It's a big story. Unfortunately, which tells you there
is something wrong. It's at least that big, and it's monolithic. You don't have to understand

or look at the text there right now to see that it's monolithic. It isn't a thing which is

Cesaro's experiment. It's not pulled out from the Monte Carlo process. It's not separated.




Let's look why. Remember, the constraint here is that every procedure return the same
value for the same arguments. Every procedure represents a function. That's a different

kind of constraint. Because when I have assignments, I can change some internal state
variable.
So let's see how that causes things to go wrong. Well, start at the beginning. The estimate

of pi looks sort of the same. What I'm doing is I take the square root of six over the random
GCD test applied to n, whereas that's what this is.





But here, we are beginning to see something funny. The random GCD test of a certain
number of trials is just like we had before, an iteration on the number of trials remaining,

the number of trials that have been passed, and another variable x.




What's that x? That x is the state of the random number generator. And it is now going to

be used here. The same random update function that I have over here is the one I would
have used in a random number generator if I were building it the other way, the one I get

out of Knuth's books. x is going to get transformed into x1, I need two random numbers.
And x1 is going to get transformed into x2, I have two random numbers. I then have to do

exactly what I did before. I take the GCD of x1 x2. If that's one, then I go around the loop
with x2 being the next value of x.





You see what's happened here is that the state of the random number generator is no
longer confined to the insides of the random number generator. It has leaked out. It has

leaked out into my procedure that does the Monte Carlo experiment. But what's worse than
that, is it's also, because it was contained inside my experiment itself, Cesaro, it leaked out

of that too. Because Cesaro called twice, has to have a different value each time, if I going
to have a legitimate experimental test. So Cesaro can't be a function either, unless I pass it

the seed of the random number generator that is going to go wandering around.




So unfortunately, the seed of random number generator has leaked out into Cesaro, from

the random number generator, that's leaked into the Monte Carlo experiment. And,
unfortunately, my Monte Carlo experiment here is no longergeneral. The Monte Carlo

experiment here knows how many random numbers I need to do the experiment.




That's sort of horrible. I lost an ability to decompose a problem into pieces, because I

wasn't willing to accept the little loop of information, the feedback process, that happens
inside the random number generator before that was made by having an assignment to a

state variable that was confined to the random number generator. So the fact that the
random number generator is an object, with an internal state variable, it's affected by

nothing, but it'll give you something, and it will apply it's force to you, that was what we're

missing now.
OK, well I think we've seen enough reason for doing this, and it all sort of looks very

wonderful. Wouldn't it be nice if assignment was a good thing and maybe it's worth it, but
I'm not sure. As Mr. Gilbert and Sullivan said, things are seldom what they seem, skim milk

masquerades as cream.




Are there any questions? Are there any philosophers here? Anybody want to argue about

objects? You're just floored, right? And you haven't done your homework yet. You haven't
come up with a good question. Oh, well. Sure, thank you. Let's take the long break now.
MIT OpenCourseWare
http://ocw.mit.edu


6.001 Structure and Interpretation of Computer Programs, Spring 2005





Please use the following citation format:

       Eric Grimson, Peter Szolovits, and Trevor Darrell, 6.001 Structure and

       Interpretation of Computer Programs, Spring 2005. (Massachusetts Institute
       of Technology: MIT OpenCourseWare).    http://ocw.mit.edu (accessed MM DD,
       YYYY). License: Creative Commons Attribution-Noncommercial-Share Alike.


Note: Please use the actual date you accessed thismaterial in your citation.



For more information about citing these materials or our Terms of Use, visit:
http://ocw.mit.edu/terms