PROFESSOR: Well today we're going to learn about something
quite amazing.
We're going to understand what we mean by a program a little
bit more profoundly than we have up till now.
Up till now, we've been thinking of programs as
describing machines.
So for example, looking at this still store, we see here
is a program for factorial.
And what it is, is a character string description, if you
will, of the wiring diagram of a
potentially infinite machine.
And we can look at that a little bit and
just see the idea.
That this is a sort of compact notation which says, if n is
0, the result is one.
Well here comes n coming into this machine, and if it's 0,
then I control this switch in such a way that the switch
allows the output to be one.
Otherwise, it's n times factorial of n minus one.
Well, I'm computing factorial of n minus one and multiplying
that by n, and, in the case that it's not 0, this switch
makes the output come from there.
Of course, this is a machine with a potentially infinite
number of parts, because factorial occurs within
factorial, so we don't know how deep it has to be.
But that's basically what our notation for programs really
means to us at this point.
It's a character string description, if you will, of a
wiring diagram that could also be drawn some other way.
And, in fact, many people have proposed to me, programming
languages look graphical like this.
I'm not sure I believe there are many advantages.
The major disadvantage, of course, is that it takes up
more space on a page, and, therefore, it's harder to pack
into a listing or to edit very well.
But in any case, there's something very remarkable that
can happen in the competition world which is that you can
have something called a universal machine.
If we look at the second slide, what we see is a
special machine called eval.
There is a machine called eval, and I'm going to show it
to you today.
It's very simple.
What is remarkable is that it will fit on the blackboard.
However, eval is a machine which takes as input a
description of another machine.
It could take the wiring diagram of a
factorial machine as input.
Having done so, it becomes a simulator for the factorial
machine such that, if you put a six in, out comes a 720.
That's a very remarkable sort of machine.
And the most amazing part of it is that it fits on a
blackboard.
By contrast, one could imagine in the analog electronics
world a very different machine, a machine which also
was, in some sense, universal, where you gave a circuit
diagram as one of the inputs, for example, of this little
low-pass filter, one-pole low-pass filter.
And you can imagine that you could, for
example, scan this out--
the scan lines are the signal that's describing what this
machine is to simulate--
then the analog of that which is made out of electrical
circuits, should configure itself into a filter that has
the frequency response specified
by the circuit diagram.
That's a very hard machine to make, and, surely, there's no
chance that I could put it on a blackboard.
So we're going to see an amazing thing today.
We're going to see, on the blackboard,
the universal machine.
And we'll see that among other things, it's extremely simple.
Now, we're getting very close to the real spirit in the
computer at this point.
So I have to show a certain amount of reverence and
respect, so I'm going to wear a suit jacket for the only
time that you'll ever see me wear a suit jacket here.
And I think I'm also going to put on an appropriate hat for
the occasion.
Now, this is a lecturer which I have to warn you--
let's see, normally, people under 40 and who don't have
several children are advised to be careful.
If they're really worried, they should leave. Because
there's a certain amount of mysticism that will appear
here which may be disturbing and cause
trouble in your minds.
Well in any case, let's see, I wish to write for you the
evaluator for Lisp.
Now the evaluator isn't very complicated.
It's very much like all the programs we've seen already.
That's the amazing part of it.
It's going to be-- and I'm going to write it right here--
it's a program called eval.
And it's a procedure of two arguments in expression of an
environment.
And like every interesting
procedure, it's a case analysis.
But before I start on this, I want to tell you some things.
The program we're going to write on the blackboard is
ugly, dirty, disgusting, not the way I would write this is
a professional.
It is written with concrete syntax, meaning you've got
really to use lots of CARs and CDRs which is exactly what I
told you not to do.
That's on purpose in this case, because I want it to be
small, compact, fit on the blackboard so you can get the
whole thing.
So I don't want to use long names like I normally use.
I want to use CAR-CDR because it's short.
Now, that's a trade-off.
I don't want you writing programs like this.
This is purely for an effect.
Now, you're going to have to work a little harder to read
it, but I'm going to try to make it clear
as I'm writing it.
I'm also--
this is a pretty much complete interpreter, but there's going
to be room for putting in more things--
I'm going to leave out definition and assignment,
just because they are not essential, for a mathematical
reason I'll show you later and also they take up more space.
But, in any case, what do we have to do?
We have to do a dispatch which breaks the types of
expressions up into particular classes.
So that's what we're going to have here.
Well, what expressions are there?
Let's look at the kinds of expressions.
We can have things like the numeral three.
What do I want that to do?
I can make choices, but I think right now, I want it to
be a three.
That's what I want.
So that's easy enough.
That means I want, if the thing is a number, the
expression, that I want the expression
itself as the answer.
Now the next possibility is things that we
represent as symbols.
Examples of symbols are things like x, n, eval, number, x.
What do I mean them to be?
Those are things that stand for other things.
Those are the variables of our language.
And so I want to be able to say, for example, that x, for
example, transforms to it's value which might be three.
Or I might ask something like car.
I want to have as its value--
be something like some procedure, which I don't know
what is inside there, perhaps a machine language code or
something like that.
So, well, that's easy enough.
I'm going to push that off on someone else.
If something is a symbol, if the expression is a symbol,
then I want the answer to be the result, looking up the
expression in the environment.
Now the environment is a dictionary which maps the
symbol names to their values.
And that's all it is.
How it's done?
Well, we'll see that later.
It's very easy.
It's easy to make data structures that are tables of
various sorts.
But it's only a table, and this is the access routine for
some table.
Well, the next thing, another kind of expression--
you have things that are described constants that are
not numbers, like 'foo.
Well, for my convenience, I want to syntactically
transform that into a list structure which is, quote foo.
A quoted object, whatever it is, is going to be actually an
abbreviation, which is not part of the evaluator but
happens somewhere else, an abbreviation for an expression
that looks like this.
This way, I can test for the type of the expression as
being a quotation by examining the car of the expression.
So I'm not going to worry about that in the evaluator.
It's happening somewhere earlier in
the reader or something.
If the expression of the expression is quote, then what
I want, I want quote foo to itself evaluate to foo.
It's a constant.
This is just a way of saying that this evaluates to itself.
What is that?
That's the second of the list. It's the second element of the
list. The second element of the list is it's CADR. So I'm
just going to write here, CADR.
What else do we have here?
We have lambda expressions, for example,
lambda of x plus x y.
Well, I going have to have some representation for the
procedure which is the value of an expression, of a lambda
expression.
The procedure here is not the expression lambda x.
That's the description of it, the textual description.
However, what what I going to expect to see here is
something which contains an environment as one of its
parts if I'm implementing a lexical language.
And so what I'd like to see is some type flags.
I'm going to have to be able to distinguish procedures
later, procedures which were produced by lambdas, from ones
that may be primitive.
And so I'm going to have some flag, which I'll just
arbitrarily call closure, just for historical reasons.
Now, to say what parts of this are important.
I'm going to need to know the bound variable
list and the body.
Well, that's the CDR of this, so it's going to be x and plus
x y and some environment.
Now this is not something that users should ever see, this is
purely a representation, internally,
for a procedure object.
It contains a bound variable list, a body, and an
environment, and some type tag saying, I am a procedure.
I'm going to make one now.
So if the CAR of the expression is quote lambda,
then what I'm going to put here is--
I'm going to make a list of closure, the CDR of the
procedure description was everything except the lambda,
and the current environment.
This implements the rule for environments in the
environment model.
It has to do with construction of procedures from lambda
expressions.
The environment that was around at the time the
evaluator encountered the lambda expression is the
environment where the procedure resulting interprets
it's free variables.
So that's part of that.
And so we have to capture that environment as part of the
procedure object.
And we'll see how that gets used later.
There are also conditional expressions of things like
COND of say, p one, e one, p two, e two.
Where this is a predicate, a predicate is a thing that is
either true or false, and the expression to be evaluated if
the predicate is true.
A set of clauses, if you will, that's the
name for such a thing.
So I'm going put that somewhere else.
We're going to worry about that in another piece of code.
So EQ--
if the CAR of the expression is COND, then I'm going to do
nothing more than evaluate the COND, the CDR of the
expression.
That's all the clauses in the environment that I'm given.
Well, there's one more case, arbitrary thing like the sum
of x and three, where this is an operator applied to
operands, and there's nothing special about it.
It's not one of the special cases, the special forms.
These are the special forms.
And if I were writing here a professional program, again, I
would somehow make this data directed.
So there wouldn't be a sequence of conditionals here,
there'd be a dispatch on some bits if I were trying to do
this in a more professional way.
So that, in fact, I can add to the thing without changing my
program much.
So, for example, they would run fast, but I'm not worried
about that.
Here we're trying to look at this in its entirety.
So it's else.
Well, what do we do?
In this case, I have to somehow do an addition.
Well, I could find out what the plus is.
I have to find out what the x and the three are.
And then I have to apply the result of finding what the
plus is to the result of finding out what the x
and the three are.
We'll have a name for that.
So I'm going to apply the result of evaluating the CAR
of the expression--
the car of the expression is the operator--
in the environment given.
So evaluating the operator gets me the procedure.
Now I have to evaluate all the operands to get the arguments.
I'll call that EVLIST, the CDR of the operands, of the
expression, with respect to the environment.
EVLIST will come up later--
EVLIST, apply, COND pair, COND, lambda, define.
So that what you are seeing here now is pretty much all
there is in the evaluator itself.
It's the case dispatch on the type of the expression with
the default being a general application or a combination.
Now there is lots of things we haven't defined yet.
Let's just look at them and see what they are.
We're going to have to do this later, evcond.
We have to write apply.
We're going to have to write EVLIST. We're
going to write LOOKUP.
I think that's everything, isn't there?
Everything else is something which is simple, or primitive,
or something like that.
And, of course, we could many more special forms here, but
that would be a bad idea in general in a language.
You make a language very complicated by putting a lot
of things in there.
The number of reserve words that should exist in a
language should be no more than a person could remember
on his fingers and toes.
And I get very upset with languages which have hundreds
of reserve words.
But that's where the reserve words go.
Well, now let's get to the next part of
this, the kernel, apply.
What else is this doing?
Well, apply's job is to take a procedure and apply it to its
arguments after both have been evaluated to come up with a
procedure and the arguments rather the operator symbols
and the operand symbols, whatever they are--
symbolic expressions.
So we will define apply to be a procedure of two arguments,
a procedure and arguments.
And what does it do?
It does nothing very complicated.
It's got two cases.
Either the procedure is primitive--
And I don't know exactly how that is done.
It's possible there's some type information just like we
made closure for, here, being the description of the type of
a compound thing--
probably so.
But it is not essential how that works, and, in fact, it
turns out, as you probably know or have deduced, that you
don't need any primitives anyway.
You can compute anything without them because some of
the lambda that I've been playing with.
But it's nice to have them.
So here we're going to do some magic which I'm
not going to explain.
Go to machine language, apply primop.
Here's how it adds.
Execute an add instruction.
However, the interesting part of a language is the glue by
which the predicates are glued together.
So let's look at that.
Well, the other possibility is that this is a compound made
up by executing a lambda expression, this
is a compound procedure.
Well, we'll check its type.
If it is closure, if it's one of those, then I have to do an
eval of the body.
The way I do this, the way I deal with this at all, is the
way I evaluate the application of a procedure to its
arguments, is by evaluating the body of the procedure in
the environment resulting from extending the environment of
the procedure with the bindings of the formal
parameters of the procedure to the arguments that
were passed to it.
That was a long sentence.
Well that's easy enough.
Now here's going to be a lot of CAR-CDRing.
I have to get the body of the procedure.
Where's the body of the procedure in here?
Well here's the CAR, here's the CDR is the
whole rest of this.
So here's the CADR. And so I see, what I have here is the
body is the second element of the second
element of the procedure.
So it's the CADR of the CADR or the CADADR.
It's the C-A-D-A-D-R, CADADR of the procedure.
To evaluate the body in the result of binding that's
making up more environment, well I need the formal
parameters of the of the procedure, what is that?
That's the CAR of the CDR. It's horrible isn't it?
--of the procedure.
Bind that to the arguments that were passed in the
environment, which is passed also as part of the procedure.
Well, that's the CAR of the CDR of the CDR of this,
CADADR, of the procedure.
Bind, eval, pair, COND, lamda, define--
Now, of course, if I were being really a neat character,
and I was being very careful, I would actually put an extra
case here for checking for certain errors like, did you
try to apply one to an argument?
You get a undefined procedure type.
So I may as well do that anyway.
--else, some sort of error, like that.
Now, of course, again, in some sort of more real system,
written for professional reasons, this would be written
with a case analysis done by some sort of dispatch.
Over here, I would probably have other cases like, is this
compiled code?
It's very important.
I might have distinguished the kind of code that's produced
by a directly evaluating a lambda in interpretation from
code that was produced by somebody's compiler or
something like that.
And we'll talk about that later.
Or is this a piece Fortran program I have
to go off and execute.
It's a perfectly possible thing, at this
point, to do that.
In fact, in this concrete syntax evaluator I'm writing
here, there's an assumption built in that this is Lisp,
because I'm using CARs and CDRs.
CAR means the operator, and CDR means the operand.
In the text, there is an abstract syntax evaluator for
which these could be--
these are given abstract names like operator, and operand,
and all these other things are like that.
And, in that case, you could reprogram it to be ALGOL with
no problem.
Well, here we have added another couple of things that
we haven't defined.
I don't think I'll worry about these at all, however, this
one will be interesting later.
Let's just proceed through this and get it done.
There's only two more blackboards so it
can't be very long.
It's carefully tailored to exactly fit.
Well, what do we have left?
We have to define EVLIST, which is over here.
And EVLIST is nothing more than a map down a bunch of
operands producing arguments.
But I'm going to write it out.
And one of the reasons I'm going to write this out is for
a mystical reason, which is I want to make this evaluator so
simple that it can understand itself.
I'm going to really worry about that a little bit.
So let's write it out completely.
See, I don't want to worry about whether or not the thing
can pass functional arguments.
The value evaluator is not going to use them.
The evaluator is not going to produce functional values.
So even if there were a different, alternative
language that were very close to this, this evaluates a
complex language like Scheme which does allow procedural
arguments, procedural values, and procedural data.
But even if I were evaluating ALGOL, which doesn't allow
procedural values, I could use this evaluator.
And this evaluator is not making any
assumptions about that.
And, in fact, if this value were to be restricted to not
being able to that, it wouldn't matter, because it
doesn't use any of those clever things.
So that's why I'm arranging this to be super simple.
This is sort of the kernel of all possible language
evaluators.
How about that?
Evlist--
well, what is it?
It's the procedure of two arguments, l and an
environment, where l is a list such that if the list of
arguments is the empty list, then the result is the empty
list. Otherwise, I want to cons up the result of
evaluating the CAR of the list of operands in the
environment.
So I want the first operand evaluated, and I'm going to
make a list of the results by CONSing that onto the result
of this EVLISTing as a CDR recursion, the CDR of the list
relative to the same environment.
Evlist, cons, else, COND, lambda, define--
And I have one more that I want to put on the blackboard.
It's the essence of this whole thing.
And there's some sort of next layer down.
Conditionals--
conditionals are the only thing left that are sort of
substantial.
Then below that, we have to worry about things like lookup
and bind, and we'll look at that in a second.
But of the substantial stuff at this level of detail, next
important thing is how you deal with conditionals.
Well, how do we have a conditional thing?
It's a procedure of a set of clauses and an environment.
And what does it do?
It says, if I've no more clauses, well, I have to give
this a value.
It could be that it was an error.
Supposing it run off the end of a
conditional, it's pretty arbitrary.
It's up to me as programmer to choose what I want to happen.
It's convenient for me, right now, to write down that this
has a value which is the empty list, doesn't matter.
For error checking, some people might
prefer something else.
But the interesting things are the following ones.
If I've got an else clause--
You see, if I have a list of clauses, then each clause is a
list. And so the predicate part is
the CAAR of the clauses.
It's the CAR, which is the first part of the first clause
in the list of clauses.
If it's an else, then it means I want my result of the
conditional to be the result of evaluating the matching
expression.
So I eval the CADR. So this is the first clause, the second
element of it, CADAR--
CADAR of a CAR--
of the clauses, with respect to the environment.
Now the next possibility is more interesting.
If it's false, if the first predicate in the predicate
list is not an else, and it's not false, if it's not the
word else, and if it's not a false thing--
Let's write down what it is if it's a false thing.
If the result of evaluating the first
predicate, the clauses--
respect the environment, if that evaluation yields false,
then it means, I want to look at the next clause.
So I want to discard the first one.
So we just go around loop, evcond, the CDR of the clauses
relative to that environment.
And otherwise, I had a true clause, in which case, what I
want is to evaluate the CADAR of the clauses relative to
that environment.
Boy, it's almost done.
It's quite close to done.
I think we're going to finish this part off.
So just buzzing through this evaluator, but so far you're
seeing almost everything.
Let's look at the next transparency here.
Here is bind.
Bind is for making more table.
And what we are going to do here is make a--
we're going to make a no-frame for an environment structure.
The environment structure is going to be represented as a
list of frames.
So given an existing environment structure, I'm
going to make a new environment structure by
consing a new frame onto the existing environment
structure, where the new frame consists of the result of
pairing up the variables, which are the bound variables
of the procedure I'm applying, to the values which are the
arguments that were passed that procedure.
This is just making a list, adding a new element to our
list of frames, which is an environment structure, to make
a new environment.
Where pair-up is very simple.
Pair-up is nothing more than if I have a list of variables
and a list of values, well, if I run out of variables and if
I run out of values, everything's OK.
Otherwise, I've given too many arguments.
If I've not run out of variables, but I've run out of
values, that I have too few arguments.
And in the general case, where I don't have any errors, and
I'm not done, then I really am just adding a new pair of the
first variable with the first argument, the first value,
onto a list resulting from pairing-up the rest of the
variables with the rest of the values.
Lookup is of course equally simple.
If I have to look up a symbol in an environment, well, if
the environment is empty, then I've got an unbound variable.
Otherwise, what I'm going to do is use a special pair list
lookup procedure, which we'll have very shortly, of the
symbol in the first frame of the environment.
Since I know the environment is not empty, it must have a
first frame.
So I lookup the symbol in the first frame.
That becomes the value cell here.
And then, if the value cell is empty, if there is no such
value cell, then I have to continue and look at the rest
of the frames.
It means there was nothing found there.
So that's a property of ASSQ is it returns emptiness if it
doesn't find something.
but if it did find something, then I'm going to use the CDR
of the value cell here, which is the thing that was the pair
consisting of the variable and the value.
So the CDR of it is the value part.
Finally, ASSQ is something you've probably seen already.
ASSQ takes a symbol and a list of pairs, and if the list is
empty, it's empty.
If the symbol is the first thing in the list--
That's an error.
That should be CAAR, C-A-A-R. Everybody note that.
Right there, OK?
And in any case, if the symbol is the CAAR of the A list,
then I want the first, the first pair, in the A list. So,
in other words, if this is the key matching the right entry,
otherwise, I want to look up that symbol in the rest. Sorry
for producing a bug, bugs appear.
Well, in any case, you're pretty much seeing
the whole thing now.
It's a very beautiful thing, even though it's written in an
ugly style, being the kernel of every language.
I suggest that we just--
let's look at it for a while.
[MUSIC PLAYING]
Are there any questions?
Alright, I suppose it's time to take a small break then.
[MUSIC PLAYING]
OK, now we're just going to do a little bit of practice
understanding what it is we've just shown you.
What we're going to do is go through, in detail, an
evaluation by informally substituting through the
interpreter.
And since we have no assignments or definitions in
this interpreter, we have no possible side effects, and so
the we can do substitution with impunity and not worry
about results.
So the particular problem I'd like to look at is it an
interesting one.
It's the evaluation of quote, open, open, open, lambda of x,
lambda of y plus x y, lambda, lambda, applied to three,
applied to four, in some global environment
which I'll call e0.
So what we have here is a procedure of one argument x,
which produces as its value a procedure of one argument y,
which adds x to y.
We are applying the procedure of one argument x to three.
So x should become three.
And the result of that should be procedure of one argument
y, which will then apply to 4.
And there is a very simple case, they will
then add those results.
And now in order to do that, I want to make a very simple
environment model.
And at this point, you should already have in your mind the
environments that this produces.
But we're going to start out with a global environment,
which I'll call e0, which is that.
And it's going to have in it things, definitions for plus,
and times, and--
using Greek letters, isn't that
interesting, for the objects--
and minus, and quotient, and CAR, and CDR, and CONS, and
EQ, and everything else you might imagine in a global
environment.
It's got something there for each of those things,
something the machine is born with, that's e0.
Now what does it mean to do this evaluation?
Well, we go through the set of special forms. First of all,
this is not a number.
This is not a symbol.
Gee, it's not a quoted expression.
This is a quoted expression, but that's not what I
interested in.
The question is, whether or not the thing which is quoted
is quoted expression?
I'm evaluating an expression.
This just says it's this particular expression.
This is not a quoted expression.
It's not a thing that begins with lambda.
It's not a thing that begins with COND.
Therefore, it's an application of its
of an operated operands.
It's a combination.
The combination thus has this as the operator and this is
the operands.
Well, that means that what I'm going to do is transform this
into apply of eval, of quote, open, open lambda of
x, lambda of y--
I'm evaluating the operator--
plus x y, in the environment, also e0, with the operands
that I'm going to apply this to, the arguments being the
result of EVLIST, the list containing four, fin e0.
I'm using this funny notation here for e0 because this
should be that environment.
I haven't a name for it, because I have no environment
to name it in.
So this is just a representation of what would
be a quoted expression, if you will.
The data structure, which is the environment, goes there.
Well, that's what we're seeing here.
Well in order to do this, I have to do this, and
I have to do that.
Well this one's easy, so why don't we do that one first.
This turns into apply of eval-- just
copying something now.
Most of the substitution rule is copying.
So I'm going to not say the words when I copy, because
it's faster.
And then the EVLIST is going to turn into a cons, of eval,
of four, in e0--
because it was not an empty list--
onto the result of EVLISTing, on the empty list, in e0.
And I'm going to start leaving out steps soon, because it's
going to get boring.
But this is basically the same thing as apply, of eval--
I'm going to keep doing this--
the lambda of x, the lambda of y, plus xy, 3, close, e0.
I'm a pretty good machine.
Well, eval of four, that's meets the
question, is it a number.
So that's cons, cons of 4.
And EVLIST of the empty list is the empty
list, so that's this.
And that's very simple to understand, because that means
the list containing four itself.
So this is nothing more than apply of eval, quote, open,
open, lambda of x, lambda of y, plus x y, three applied to,
e0, applied to the list four--
bang.
So that's that step.
Now let's look at the next, more interesting thing.
What do I do to evaluate that?
Evaluating this means I have to evaluate--
Well, it's not.
It's nothing but an application.
It's not one of the special things.
If the application of this operator, which we see here--
here's the operator--
applied to this operands, that combination.
But we know how to do that, because that's the last case
of the conditional.
So substituting in for this evaluation, it's apply of eval
of the operator in the EVLIST of the operands.
Well, it's apply, of apply, of eval, of quote, open, lambda
of x, lambda of y, plus x y, lambda, lambda,
in environment e0.
I'm going to short circuit the evaluation of the operands ,
because they're the same as they were before.
I got a list containing three, apply that, and
apply that to four.
Well let's see.
Eval of a lambda expression produces a procedure object.
So this is apply, of apply, of the procedure object closure,
which contains the body of the procedure, x, which is
lambda-- which binds x [UNINTELLIGIBLE]
the internals of the body, it returns the procedure of one
argument y, which adds x to y.
Environment e0 is now captured in it, because this was
evaluated with respect to e0.
e0 is part now of the closure object.
Apply that to open, three, close, apply, to open, 4,
close, apply.
So going from this step to this step meant that I made up
a procedure object which captured in it e0 as part of
the procedure object.
Now, we're going to pass those to apply.
We have to apply this procedure
to that set of arguments.
Well, but that procedure is not primitive.
It's, in fact, a thing which has got the tag closure, and,
therefore, what we have to do is do a bind.
We have to bind.
A new environment is made at this point, which has as its
parent environment the one over here, e0, that
environment.
And we'll call this one, e1.
Now what's bound in there?
x is bound to three.
So I have x equal three.
That's what's in there.
And we'll call that e1.
So what this transforms into is an eval of the body of
this, which is this, the body of that procedure, in the
environment that you just saw.
So that's an apply, of eval, quote, open, lambda of y, plus
x y-- the body--
in e1.
And apply the result of that to four, open, close, 4--
list of arguments.
Well, that's sensible enough because evaluating a lambda, I
know what to do.
That means I apply, the procedure which is closure,
binds one argument y, adds x to y, with e1 captured in it.
And you should really see this.
I somehow manufactured a closure.
I should've put this here.
There was one over here too.
Well, there's one here now.
I've captured e1, and this is the procedure of one argument
y, whatever this is.
That's what that is there, that closure.
I'm going to apply that to four.
Well, that's easy enough.
That means I have to make a new environment by copying
this pointer, which was the pointer of the procedure,
which binds y equal 4 with that environment.
And here's my new environment, which I'll call e2.
And, of course, this application then is evaluate
the body in e2.
So this is eval, the body, which is plus x y, in the
environment e2.
But this is an application, so this is the apply, of eval,
plus in e2, an EVLIST, quote, open, x y, in e2.
Well, but let's see.
That is apply, the object which is a
result of that and plus.
So here we are in e2, plus is not here, it's not here, oh,
yes, but's here as some primitive operator.
So it's the primitive operator for addition.
Apply that to the result of evaluating x and y in e2.
But we can see that x is three and y is four.
So that's a three and four, here.
And that magically produces for me a seven.
I wanted to go through this so you would see, essentially,
one important ingredient, which is what's being passed
around, and who owns what, and what his job is.
So what do we have here?
We have eval, and we have apply, the two main players.
And there is a big loop the goes around like this.
Which is eval produces a procedure and
arguments for apply.
Now some things eval could do by itself.
Those are little self things here.
They're not interesting.
Also eval evaluates all of the arguments, one after another.
That's not very interesting.
Apply can apply some procedures like plus, not very
interesting.
However, if apply can't apply a procedure like plus, it
produces an expression and environment for eval.
The procedural arguments wrap up essentially the state of a
computation and, certainly, the expression of environment.
And so what we're actually going to do next is not the
complete state, because it doesn't say
who wants the answers.
But what we're going to do-- it's always got something like
an expression of environment or procedure and arguments as
the main loop that we're going around.
There are minor little sub loops like eval through
EVLIST, or eval through evcond, or apply through a
primitive apply.
But they're not the essential things.
So that's what I wanted you to see.
Are there any questions?
Yes.
AUDIENCE: I'm trying to understand how x got down to
three instead of four.
At the early part of the--
PROFESSOR: Here.
You want to know how x got down to three?
AUDIENCE: Because x is the outer procedure, and x and y
are the inner procedure.
PROFESSOR: Fine.
Well, I was very careful and mechanical.
First of all, I should write those procedures again for
you, pretty printed.
First order of business, because you're probably not
reading them well.
So I have here that procedure of--
was it x over there--
which is--
value of that procedure of y, which adds x to y, lambda,
lambda, applied that to three, takes the result of that, and
applied that to four.
Is that not what I wrote?
Now, you should immediately see that here is an
application--
let me get a white piece of chalk--
here is an application, a combination.
That combination has this as the operator
and this as the operand.
The three is going in for the x here.
The result of this is a procedure of one argument y,
which gets applied to four.
So you just weren't reading the expression right.
The way you see that over here is that here I have the actual
procedure object, x.
It's getting applied to three, the list containing three.
What I'm left over with is something which
gets applied to four.
Are there any other questions?
Time for our next small break then.
Thank you.
[MUSIC PLAYING]
Let's see, at this point, you should be getting the feeling,
what's this nonsense this Sussman
character is feeding me?
There's an awful lot of strange nonsense here.
After all, he purported to explain to me Lisp, and he
wrote me a Lisp program on the blackboard.
The Lisp program was intended to be interpreted for Lisp,
but you need a Lisp interpreter in order to
understand that program.
How could that program have told me anything there is to
be known about Lisp?
How is that not completely vacuous?
It's a very strange thing.
Does it tell me anything at all?
Well, you see, the whole thing is sort of like these Escher's
hands that we see on this slide.
Yes, eval and apply each sort of draw each other and
construct the real thing, which can sit
out and draw itself.
Escher was a very brilliant man, he just didn't know the
names of these spirits.
Well, I'm going to do now, is I'm going to try to convince
you that both this mean something, and, as a aside,
I'm going to show you why you don't need definitions.
Just turns out that that sort of falls out, why definitions
are not essential in a mathematical sense for doing
all the things we need to do for computing.
Well, let's see here.
Consider the following small program, what does it mean?
This is a program for computing exponentials.
The exponential of x to the nth power is if--
and is zero, then the result is one.
Otherwise, I want the product of x and the result of
exponentiating x to the n minus one power.
I think I got it right.
Now this is a recursive definition.
It's a definition of the exponentiation procedure in
terms of itself.
And, as it has been mentioned before, your high school
geometry teacher probably gave you a hard time
about things like that.
Was that justified?
Why does this self referential definition make any sense?
Well, first of all, I'm going to convince you that your high
school geometry teacher was I telling you nonsense.
Consider the following set of definitions here.
x plus y equals three, and x minus y equal one.
Well, gee, this tells you x in terms of y, and this one tells
you y in terms of x, presumably.
And yet this happens to have a unique solution in x and y.
However, I could also write two x plus two y is six.
These two equations have an infinite number solutions.
And I could write you, for example, x minus y equal 2,
and these two equations have no solutions.
Well, I have here three sets of simultaneous linear
equations, this set, this set, and this set.
But they have different numbers of solutions.
The number of solutions is not in the form of the equations.
They all three sets have the same form.
The number of solutions is in the content.
I can't tell by looking at the form of a definition whether
it makes sense, only by its detailed content.
What are the coefficients, for example, in the
case of linear equations?
So I shouldn't expect to be able to tell looking at
something like this, from some simple things like, oh yes,
EXPT is the solution of this recursion equation.
Expt is the procedure which if substituted in here,
gives me EXPT back.
I can't tell, looking at this form, whether or not there's a
single, unique solution for EXPT, an infinite number of
solutions, or no solutions.
It's got to be how it counts and things
like that, the details.
And it's harder in programming than linear algebra.
There aren't too many theorems about it in programming.
Well, I want to rewrite these equations a little
bit, these over here.
Because what we're investigating is
equations like this.
But I want to play a little with equations like this that
we understand, just so we get some insight into
this kind of question.
We could rewrite our equations here, say these two, the ones
that are interesting, as x equals three minus y, and y
equals x minus one.
What do we call this transformation?
This is a linear transformation, t.
Then what we're getting here is an equation x y
equals t of x y.
What am I looking for?
I'm looking for a fixed point of t.
The solution is a fixed point of t.
So the methods we should have for looking for solutions to
equations, if I can do it by fixed points, might be
applicable.
If I have a means of finding a solution to an equations by
fixed points--
just, might not work--
but it might be applicable to investigating solutions of
equations like this.
But what I want you to feel is that this is an equation.
It's an expression with several instances of various
names which puts a constraint on the name, saying what that
name could have as its value, rather than some sort of
mechanical process of substitution right now.
This is an equation which I'm going to try to solve.
Well, let's play around and solve it.
First of all, I want to write down the function which
corresponds to t.
First I want to write down the function which corresponds to
t whose fixed point is the answer to this question.
Well, let's consider the following procedure f.
I claim it computes that function.
f is that procedure of one argument g, which is that
procedure of two arguments x and n.
Which have the property that if n is zero, then the result
is one, otherwise, the result is the product of x and g,
applied to x, and minus n1.
g, times, else, COND, lambda, lambda--
Here f is a procedure, which if I had a solution to that
equation, if I had a good exponentiation procedure, and
I applied f to that procedure, then the result would be a
good exponentiation procedure.
Because, what does it do?
Well, all it is is exposing g were a good exponentiation
procedure, well then this would produce, as its value, a
procedure to arguments x and n, such that if n were 0, the
result would be one, which is certainly true of
exponentiation.
Otherwise, it will be the result of multiplying x by the
exponentiation procedure given to me with x and n minus one
as arguments.
So if this computed the correct exponentiation for n
minus one, then this would be the correct exponentiation for
exponent n, so this would have been the right
exponentiation procedure.
So what I really want to say here is E-X-P-T is a fixed
point of f.
Now our problem is there might be more than one fixed point.
There might be no fixed points.
I have to go hunting for the fixed points.
Got to solve this equation.
Well there are various ways to hunt for fixed points.
Of course, the one we played with at the beginning of this
term worked for cosine.
Go into radians mode on your calculator and push cosine,
and just keep doing it, and you get to some number which
is about 0.73 or 0.74.
I can't remember which.
By iterating a function, whose fixed point I'm searching for,
it is sometimes the case that that function will converge in
producing the fixed point.
I think we luck out in this case, so let's look for it.
Let's look at this slide.
Consider the following sequence of procedures.
e0 over here is the procedure which does nothing at all.
It's the procedure which produces an error for any
arguments you give it.
It's basically useless.
Well, however, I can make an approximation.
Let's consider it the worst possible approximation to
exponentiation, because it does nothing.
Well, supposing I substituted e0 for g by calling f, as you
see over here on e0.
So you see over here, have e0 there.
Then gee, what's e1?
e1 is a procedure which exponentiate things to the 0th
power, with no trouble.
It gets the right answer, anything to the zero is one,
and it makes an error on anything else.
Well, now what if I take e1 and I substitute if for g by
calling f on e1?
Oh gosh, I have here a procedure of two arguments.
Now remember e1 was appropriate for taking
exponentiations of 0, for raising to the 0 exponent.
So here, is n is 0, the result is one, so this guy is good
for that too.
However, I can use something for raising to the 0th power
to multiply it by x to raise something to the first power.
So e2 is good for both power 0 and one.
And e3 is constructed from e2 in the same way.
And e3, of course, by the same argument is good for powers 0,
one, and two.
And so I will assert for you, without proof, because the
proof is horribly difficult.
And that's the sort of thing that people called
denotational semanticists do.
This great idea was invented by Scott and Strachey.
They're very famous mathematician types who
invented the interpretation for these programs that we
have that I'm talking to you about right now.
And they proved, by topology that there is such a fixed
point in the cases that we want.
But the assertion is E-X-P-T is limit as n goes
to infinity of em.
and And that we've constructed this by the following way.
--is Well, it's f of, f of, f of, f of, f of--
f applied to anything at all.
It didn't matter what that was, because, in fact, this
always produces an error.
Applied to this--
That's by infinite nesting of f's.
So now my problem is to make some infinite things.
We need some infinite things.
How am I going to nest up an f an infinite number of times?
I'd better construct this.
Well, I don't know.
How would I make an infinite loop at all?
Let's take a very simple infinite loop, the simplest
infinite loop imaginable.
If I were to take that procedure of one argument x
which applies x to x and apply that to the procedure of one
argument x which applies x to x, then this
is an infinite loop.
The reason why this is an infinite loop is as follows.
The way I understand this is I substitute the argument for
the formal parameter in the body.
But if I do that, I take for each of these x's, I
substitute one of these, making a copy of the original
expression I just started with, the
simplest infinite loop.
Now I want to tell you about a particular operator which is
constructed by a perturbation from this infinite loop.
I'll call it y.
This is called Curry's Paradoxical Combinator of y
after a fellow by the name of Curry, who was a logician of
the 1930s also.
And if I have a procedure of one argument f, what's it
going to have in it?
It's going to have a kind of infinite loop in it, which is
that procedure of one argument x which applies f to x of x,
applied to that procedure of one argument x, which applies
f to f of x.
Now what's this do?
Suppose we apply y to F. Well, that's easy enough.
That's this capital F over here.
Well, the easiest thing to say there is, I
substitute F for here.
So that's going to give me, basically--
because then I'm going to substitute this for x in here.
Let me actually do it in steps, so you can see it
completely.
I'm going to be very careful.
This is open, open, lambda of x , capital F, x, x, applied
to itself, F of x of x.
Substituting this for this in here, this is F applied to--
what is it--
substituting this in here, open, open, lambda of x, F, of
x and x, applied to lambda of x, F of x of x, F, lambda,
pair, F.
Oh, but what is this?
This thing over here that I just computed, is
this thing over here.
But I just wrapped another F around it.
So by applying y to F, I make an infinite series of F's.
If I just let this run forever, I'll just keep making
more and more F's outside.
I ran an infinite loop which is useless, but it doesn't
matter that the inside is useless.
So y of F is F applied to y of F. So y is a magical thing
which, when applied to some function, produces the object
which is the fixed point of that function, if it exists,
and if this all works.
Because, indeed, if I take y of F and put it into F, I get
y of F out.
Now I want you to think this in terms of the eval-apply
interpreter for a bit.
I wrote down a whole bunch of recursion equations out there.
They're simultaneous in the same way these are
simultaneous equations.
Exponentiation was not a simultaneous equation.
It was only one variable I was looking for a meaning for.
But what Lisp is is the fixed point of the process which
says, if I knew what Lisp was and substituted it in for
eval, and apply, and so on, on the right hand sides of all
those recursion equations, then if it was a real good
Lisp, is a real one, then the left hand side
would also be Lisp.
So I made sense of that definition.
Now whether or not there's an answer isn't so obvious.
I can't attack that.
Now these arguments that I'm giving you
now are quite dangerous.
Let's look over here.
These are limit arguments.
We're talking about limits, and it's really calculus, or
topology, or something like that, a kind of analysis.
Now here's an argument that you all believe.
And I want to make sure you realize that I could be
bullshitting you.
What is this?
u is the sum of 1/2, 1/4, and 1/8, and so on, the sum of a
geometric series.
And, of course, I could play a game here.
u minus one is 1/2, plus 1/4, plus 1/8, and so on.
What I could do here--
oops.
There is a parentheses error here.
But I can put here two times u minus one is one plus 1/2,
plus 1/4, plus 1/8.
Can I fix that?
Yes, well.
But that gives me back two times u minus one is u,
therefore, we conclude that u is two.
And this actually is true.
There's no problem like that.
But supposing I did something different.
Supposing I start up with something which
manifestly has no sum.
v is one, plus two, plus four, plus 8, plus dot, dot, dot.
Well, v minus one is surely two, plus four, plus eight,
plus dot, dot, dot.
v minus one over two, gee, that looks like v again.
From that I should be able to conclude that--
that's also wrong, apparently.
v equals minus one.
That should be a minus one.
And that's certainly a false conclusion.
So when you play with limits, arguments that may work in one
case they may not work in some other case.
You have to be very careful.
The arguments have to be well formed.
And I don't know, in general, what the story is about
arguments like this.
We can read a pile of topology and find out.
But, surely, at least you understand now, why it might
be some meaning to the things we've been writing on the
blackboard.
And you understand what that might mean.
So, I suppose, it's almost about time for you to merit
being made a member of the grand recursive order of
lambda calculus hackers.
This is the badge.
Because you now understand, for example, what it says at
the very top, y F equals F y F. Thank you.
Are there any questions?
Yes, Lev.
AUDIENCE: With this, it seems that then there's no need to
define, as you imply, to just remember a
value, to apply it later.
Defines were kind of a side-effect it
seemed in the language.
[INTERPOSING]
are order dependent.
Does this eliminate the side-effect from the
[INTERPOSING]
PROFESSOR: The answer is, this is not the way these things
were implemented.
Define, indeed is implemented as an operation that actually
modifies an environment structure, changes the frame
that the define is executed in.
And there are many reasons for that, but a lot of this has to
do with making an interactive system.
What this is saying is that if you've made a system, and you
know you're not going to do any debugging or anything like
that, and you know everything there is all at once, and you
want to say, what is the meaning of a
final set of equations?
This gives you a meaning for it.
But in order to make an interactive system, where you
can change the meaning of one thing without changing
everything else, incrementally, you can't do
that by implementing it this way.
Yes.
AUDIENCE: Another question on your danger slide.
It seemed that the two examples that you gave had to
do with convergence and non-convergence?
And that may or may not have something to do with function
theory in a way which would lead you to think of it in
terms of linear systems, or non-linear systems. How does
this convergence relate to being able to see a priori
what properties of that might be violated?
PROFESSOR: I don't know.
The answer is, I don't know under what circumstances.
I don't know how to translate that into less than an
hour of talk more.
What are the conditions under which, for which we know that
these things converge?
And v, all that was telling you that arguments that are
based on convergence are flaky if you don't know the
convergence beforehand.
You can make wrong arguments.
You can make deductions, as if you know the answer, and not
be stopped somewhere by some obvious contradiction.
AUDIENCE: So can we say then that if F is a convergent
mathematical expression, then the recursion
property can be--
PROFESSOR: Well, I think there's a technical kind of F,
there is a technical description of those F's that
have the property that when you iteratively apply them
like this, you converge.
Things that are monotonic, and continuous, and
I forgot what else.
There is a whole bunch of little conditions like that
which have this property.
Now the real problem is deducing from looking at the
F, its definition here, whether not it has those
properties, and that's very hard.
The properties are easy.
You can write them down.
You can look in a book by Joe Stoy.
It's a great book--
Stoy.
It's called, The Scott-Strachey Method of
Denotational Semantics, and it's by Joe Stoy, MIT Press.
And he works out all this in great detail, enough to
horrify you.
But it really is readable.
OK, well, thank you.
Time for the bigger break, I suppose.