[MUSIC PLAYING]
PROFESSOR: Well, let's see.
What we did so far was
a lot of fun, was
it useful for anything?
I suppose the answer
is going to be yes.
If these metacircular
interpreters are a valuable
thing to play with.
Well, there have been times I
spend 50% of my time, over a
year, trying various design
alternatives by experimenting
with them with metacircular
interpreters--
metacircular interpreters like
the sort you just saw.
Metacircular is because they
are defined in terms of
themselves in such a way that
the language they interpret
contains itself.
Such interpreters are a
convenient medium for
exploring language issues.
If you want to try adding a new
feature, it's sort of a
snap, it's easy, you just do
it and see what happens.
You play with that language for
a while you say, gee, I'm
didn't like that, you
throw it away.
Or you might want to see what
the difference is if you'd
make a slight difference in the
binding strategy, or some
more complicated things
that might occur.
In fact, these metacircular
interpreters are an excellent
medium for people exchanging
ideas about language design,
because they're pretty easy to
understand, and they're short,
and compact, and simple.
If I have some idea that I want
somebody to criticize
like say, Dan Friedman at
Indiana, I'd write a little
metacircular interpreter and
send him some network mail
with this interpreter in it.
He could whip it up on his
machine and play with it and
say, that's no good.
And then send it back to me and
say, well, why don't you
try this one, it's
a little better.
So I want to show you some
of that technology.
See, because, really, it's the
essential, simple technology
for getting started in designing
your own languages
for particular purposes.
Let's start by adding a very
simple feature to a Lisp.
Now, one thing I want
to tell you about is
features, before I start.
There are many languages that
have made a mess of themselves
by adding huge numbers
of features.
Computer scientists have a joke
about bugs that transform
it to features all the time.
But I like to think of it is
that many systems suffer from
what's called creeping
featurism.
Which is that George has a pet
feature he'd like in the
system, so he adds it.
And then Harry says, gee, this
system is no longer what
exactly I like, so I'm going
to add my favorite feature.
And then Jim adds his
favorite feature.
And, after a while, the thing
has a manual 500 pages long
that no one can understand.
And sometimes it's the same
person who writes all of these
features and produces this
terribly complicated thing.
In some cases, like editors,
it's sort of reasonable to
have lots of features, because
there are a lot of things you
want to be able to do and
many of them arbitrary.
But in computer languages, I
think it's a disaster to have
too much stuff in them.
The other alternative you get
into is something called
feeping creaturism, which is
where you have a box which has
a display, a fancy display, and
a mouse, and there is all
sorts of complexity associated
with all this fancy IO.
And your computer language
becomes a dismal, little, tiny
thing that barely works because
of all the swapping,
and disk twitching, and so on,
caused by your Windows system.
And every time you go near the
computer, the mouse process
wakes up and says, gee do you
have something for me to do,
and then it goes
back to sleep.
And if you accidentally push
mouse with you elbow, a big
puff of smoke comes out
of your computer
and things like that.
So two ways to disastrously
destroy a
system by adding features.
But try right now to add a
little, simple feature.
This actually is a good
one, and in fact,
real Lisps have it.
As you've seen, there are
procedures like plus and times
that take any number
of arguments.
So we can write things like the
sum of the product of a
and x and x, and the product
of b and x and c.
As you can see here, addition
takes three arguments or two
arguments, multiplication takes
two arguments or three
arguments, taking numbers of
arguments all of which are to
be treated in the same way.
This is a valuable thing,
indefinite numbers of arguments.
Yet the particular Lisp system
that I showed you is one where
the numbers of arguments is
fixed, because I had to match
the arguments against the
formal parameters in the
binder, where there's
a pairup.
Well, I'd like to be able to
define new procedures like
this that can have any
number of arguments.
Well there's several parts
to this problem.
The first part is coming
up with the syntactic
specification, some way of
notating the additional
arguments, of which you don't
know how many there are.
And then there's the other
thing, which is once we've
notated it, how are we going to
interpret that notation so
as to do the right thing,
whatever the right thing is?
So let's consider an example
of a sort of thing we might
want to be able to do.
So an example might be, that
I might want to be able to
define a procedure which is a
procedure of one required
argument x and a bunch of
arguments, I don't know how
many there are, called y.
So x is required, and there are
many y's, many argument--
y will be the list of them.
Now, with such a thing, we might
be able to say something
like, map--
I'm going to do something
to every one--
of that procedure of one
argument u, which multiplies x
by u, and we'll apply
that to y.
I've used a dot here to indicate
that the thing after
this is a list of all the
rest of the arguments.
I'm making a syntactic
specification.
Now, what this depends upon, the
reason why this is sort of
a reasonable thing to do, is
because this happens to be a
syntax that's used in
the Lisp reader for
representing conses.
We've never introduced
that before.
You may have seen when playing
with the system that if you
cons two things together, you
get the first, space, dot, the
second, space--
the first, space, dot, space,
the second with parentheses
around the whole thing.
So that, for example, this x dot
y corresponds to a pair,
which has got an x in
it and a y in it.
The other notations that you've
seen so far are things
like a procedure of arguments x
and y and z which do things,
and that looks like--
Just looking at the bound
variable list, it looks like
this, x, y, z, and
the empty thing.
If I have a list of arguments I
wish to match this against,
supposing, I have a list of
arguments one, two, three, I
want to match these against. So
I might have here a list of
three things, one, two, three.
And I want to match x, y, z
against one, two, three.
Well, it's clear that the one
matches the x, because I can
just sort of follow the
structure, and the two matches
the y, and the three
matches the z.
But now, supposing I were to
compare this x dot y--
this is x dot y--
supposing I compare that with
a list of three arguments,
one, two, three.
Let's look at that again.
One, two, three--
Well, I can walk along here and
say, oh yes, x matches the
one, the y matches the list,
which is two and three.
So the notation I'm choosing
here is one that's very
natural for Lisp system.
But I'm going to choose this as
a notation for representing
a bunch of arguments.
Now, there's an alternative
possibility.
If I don't want to take one
special out, or two special
ones out or something like that,
if I don't want to do
that, if I want to talk about
just the list of all the
arguments like in addition, well
then the argument list
I'm going to choose to be that
procedure of all the arguments
x which does something with x.
And which, for example, if I
take the procedure, which
takes all the arguments x and
returned the list of them,
that's list. That's the
procedure list.
How does this work?
Well, indeed what I had as the
bound variable list in this
case, whatever it is,
is being matched
against a list of arguments.
This symbol now is all
of the arguments.
And so this is the choice I'm
making for a particular
syntactic specification, for the
description of procedures
which take indefinite numbers
of arguments.
There are two cases of it,
this one and this one.
When you make syntactic
specifications, it's important
that it's unambiguous, that
neither of these can be
confused with a representation
we already have, this one.
I can always tell whether
I have a fixed number of
explicitly named arguments
made by these formal
parameters, or a fixed number
of named formal parameters
followed by a thing which picks
up all the rest of them,
or a list of all the arguments
which will be matched against
this particular formal parameter
called x, because
these are syntactically
distinguishable.
Many languages make terrible
errors in that form where
whole segments of interpretation
are cut off,
because there are syntactic
ambiguities in the language.
They are the traditional
problems with ALGOL like
languages having to do with
the nesting of ifs in the
predicate part.
In any case, now, so I've told
you about the syntax, now,
what are we going to do about
the semantics of this?
How do we interpret it?
Well this is just super easy.
I'm going to modify
the metacircular
interpreter to do it.
And that's a one liner.
There it is.
I'm changing the way
you pair things up.
Here's the procedure that
pairs the variables, the
formal parameters, with the
arguments that were passed
from the last description of the
metacircular interpreter.
And here's some things
that are the same
as they were before.
In other words, if the list of
variables is empty, then if
the list of values is empty,
then I have an empty list.
Otherwise, I have too many
arguments, that is, if I have
empty variables but
not empty values.
If I have empty values, but the
variables are not empty, I
have too few arguments.
The variables are a symbol--
interesting case--
then, what I should do is say,
oh yes, this is the special
case that I have a
symbolic tail.
I have here a thing just like
we looked over here.
This is a tail which
is a symbol, y.
It's not a nil.
It's not the empty list. Here's
a symbolic tail that is
just the very beginning
of the tail.
There is nothing else.
In that case, I wish to match
that variable with all the
values and add that to the
pairing that I'm making.
Otherwise, I go through the
normal arrangement of making
up the whole pairing.
I suppose that's very simple.
And that's all there is to it.
And now I'll answer
some questions.
The first one--
Are there any questions?
Yes?
AUDIENCE: Could you explain
that third form?
PROFESSOR: This one?
Well, maybe we should look
at the thing as a
piece of list structure.
This is a procedure which
contains a lambda.
I'm just looking at
the list structure
which represents this.
Here's x.
These are our symbols.
And then the body is
nothing but x.
If I were looking for the bound
variable list part of
this procedure, I would go
looking at the CADR, and I'd
find a symbol.
So the, naturally, which is
this pairup thing I just
showed you, is going to be
matching a symbolic object
against a list of arguments
that were passed.
And it will bind that symbol
to the list of arguments.
In this case, if I'm looking
for it, the match will be
against this in the bound
variable list position.
Now, if what this does is it
gets a list of arguments and
returns it, that's list. That's
what the procedure is.
Oh well, thank you.
Let's take a break.
[MUSIC PLAYING]
PROFESSOR: Well let's see.
Now, I'm going to tell you
about a rather more
substantial variation, one
that's a famous variation that
many early Lisps had.
It's called dynamic binding
of variables.
And we'll investigate a little
bit about that right now.
I'm going to first introduce
this by showing you the sort
of thing that would make
someone want this idea.
I'm not going to tell what it is
yet, I'm going to show you
why you might want it.
Suppose, for example, we looked
at the sum procedure
again for summing up
a bunch of things.
To be that procedure, of a term,
lower bound, method of
computing the next index, and
upper bound, such that, if a
is greater than b then the
result is 0, otherwise, it's
the sum, of the term, procedure,
applied to a and
the result of adding up, terms,
with the next a being
the a, the next procedure passed
along, and the upper
bound being passed along.
Blink, blink, blink--
Now, when I use this sum
procedure, I can use it, for
example, like this.
We can define the sum of the
powers to be, for example, sum
of a bunch of powers x to the
n, to be that procedure
of a, b, and n--
lower bound, the upper
bound, and n--
which is sum, of lambda of x,
the procedure of one argument
x, which exponentiates x to
the n, with the a, the
incrementer, and b, being
passed along.
So we're adding up x
to n, given an x.
x takes on values from a to
b, incrementing by one.
I can also write the--
That's right.
Product, excuse me.
The product of a bunch
of powers.
It's a strange name.
I'm going to leave it there.
Weird--
I write up what I have.
I'm sure that's right.
And if I want the product
of a bunch of powers--
That was 12 brain cells,
that double-take.
I can for example use the
procedure which is like sum,
which is for making products,
but it's similar to that, that
you've seen before.
There's a procedure of three
arguments again.
Which is the product of terms
that are constructed, or
factors in this case,
constructed from
exponentiating x to the n,
where I start with a, I
increment, and I go to b.
Now, there's some sort of thing
here that should disturb
you immediately.
These look the same.
Why am I writing this
code so many times?
Here I am, in the same boat
I've been in before.
Wouldn't it be nice to make
an abstraction here?
What's an example of a good
abstraction to make?
Well, I see some codes
that's identical.
Here's one, and here's
another.
And so maybe I should be
able to pull that out.
I should be able to say, oh
yes, the sum of the powers
could be written in terms
of something called
the nth power procedure.
Imagine somebody wanted to write
a slightly different
procedure that looks
like this.
The sum powers to be a procedure
of a, b, and n, as
the result of summing
up the nth power.
We're going to give a name to
that idea, for starting at a,
going by one, and ending at b.
And similarly, I might want to
write the product powers this
way, abstracting
out this idea.
I might want this.
Product powers, to be a
procedure of a, b, and n,
which is the product of the nth
power operation on a with
the incrementation and b being
my arguments for the
analogous-thing product.
And I'd like to be able to
define, I'd like to be able to
define nth power--
I'll put it over here.
I'll put it at the top.
--to be, in fact, my procedure
of one argument x which is the
result of exponentiating
x to the n.
But I have a problem.
My environment model, that is my
means of interpretation for
the language that we've defined
so far, does not give
me a meaning for this n.
Because, as you know, this n
is free in this procedure.
The environment model tells us
that the meaning of a free
variable is determined in the
environment in which this
procedure is defined.
In a way I have written it,
assuming these things are
defined on the blackboard as
is, this is defined in the
global environment, where
there is no end.
Therefore, n is unbound
variable.
But it's perfectly clear, to
most of us, that we would like
it to be this n and this n.
On the other hand,
it would be nice.
Certainly we've got to be
careful here of keeping this
to be this, and this one
over here, wherever it
is to be this one.
Well, the desire to make
this work has led to
a very famous bug.
I'll tell you about
the famous bug.
Look at this slide.
This is an idea called
dynamic binding.
Where, instead of the free
variable being interpreted in
the environment of definition
of a procedure, the free
variable is interpreted as
having its value in the
environment of the caller
of the procedure.
So what you have is a system
where you search up the chain
of callers of a particular
procedure, and, of course, in
this case, since nth power is
called from inside product
whatever it is--
I had to write our own sum
which is the analogous
procedure--
and product is presumably called
from product powers, as
you see over here, then since
product powers bind with
variable n , then nth powers
n would be derived
through that chain.
Similarly, this n, the nth power
in n in this case, would
come through nth power here
being called from inside sum.
You can see it being called
from inside sum here.
It's called term here.
But sum was called from inside
of sum powers, which bound n.
Therefore, there would be an n
available for that n to get
it's value from.
What we have below this white
line plus over here, is what's
called a dynamic binding
view of the world.
If that works, that's a
dynamic binding view.
Now, let's take a look, for
example, at just what it takes
to implement that.
That's real easy.
In fact, the very first Lisps
that had any interpretations
of the free variables at all,
had dynamic binding
interpretations for the
free variables.
APL has dynamic binding
interpretation for the free
variables, not lexical
or static binding.
So, of course, the change
is in eval.
And it's really in two places.
First of all, one thing we see,
is that things become a
little simpler.
If I don't have to have the
environment be the environment
of definition for procedure, the
procedure need not capture
the environment at the
time it's defined.
And so if we look here at this
slide, we see that the clause
for a lambda expression, which
is the way a procedure is
defined, does not make up a
thing which has a type closure
and a attached environment
structure.
It's just the expression
itself.
And we'll decompose that some
other way somewhere else.
The other thing we see is the
applicator must be able to get
the environment of the caller.
The caller of a procedure
is right here.
If the expression we're
evaluating is anpplication or
a combination, then we're going
to call a procedure
which is the value
of the operator.
The environment of the caller
is the environment we have
right here, available now.
So all I have to do is pass
that environment to the
applicator, to apply.
And if we look at that here,
the only change we have to
make is that fellow takes that
environment and uses that
environment for the purpose of
extending that environment
when abiding the formal
parameters of the procedure to
the arguments that were passed,
not an environment
that was captured in
the procedure.
The reason why the first Lisps
were implemented this way, is
the sort of the obvious,
accidental implementation.
And, of course, as usual,
people got used to
it and liked it.
And there were some people
said, this is
the way to do it.
Unfortunately that causes some
serious problems. The most
important, serious problem in
using dynamic binding is
there's a modularity crisis
that's involved it.
If two people are working
together on some big system,
then an important thing to want
is that the names used by
each one don't interfere with
the names of the other.
It's important that when I
invent some segment of code
that no one can make my code
stop working by using my names
that I use internal to my code,
internal to his code.
However, dynamic binding
violates that particular
modularity constraint
in a clear way.
Consider, for example, what
happens over here.
Suppose it was the case
that I decided to
change the word next.
Supposing somebody is writing
sum, and somebody else is
going to use sum.
The writer of sum has a choice
of what names he may use.
Let's say, I'm that writer.
Well, by gosh, just happens I
didn't want to call this next.
I called it n.
So all places where you see
next, I called it n.
Whoops.
I changed nothing about the
specifications of this
program, but this program
stops working.
Not only that, unfortunately,
this one does too.
Why do these programs
stop working?
Well, it's sort of clear.
Instead of chasing out the value
of the n that occurs in
nth power over here or over
here, through the environment
of definition, where this one is
always linked to this one,
if it was through the
environment of definition,
because here is the
definition.
This lambda expression was
executed in the environment
where that n was defined.
If instead of doing that, I have
to chase through the call
chain, then look what horrible
thing happens.
Well, this was called from
inside sum as term, term a.
I'm looking for a value of n.
Instead of getting this
one, I get that one.
So by changing the insides of
this program, this program
stops working.
So I no longer have
a quantifier,
as I described before.
The lambda symbol is supposed
to be a quantifier.
A thing which has the property
that the names that are bound
by it are unimportant, that I
can uniformly substitute any
names for these throughout this
thing, so long as they
don't occur in here, the new
names, and the meaning of this
expression should remain
unchanged.
I've just changed the meaning of
the expression by changing
the one of the names.
So lambda is no longer
a well defined idea.
It's a very serious problem.
So for that reason, I and my
buddies have given up this
particular kind of abstraction,
which I would
like to have, in favor of
a modularity principle.
But this is the kind of
experiment you can do if you
want to play with these
interpreters.
You can try them out this way,
that way, and the other way.
You see what makes
a nicer language.
So that's a very important
thing to be able to do.
Now, I would like to give you
a feeling for I think the
right thing to do is here.
How are you going to I get
this kind of power in a
lexical system?
And the answer is, of course,
what I really want is a
something that makes up for
me an exponentiator for a
particular n.
Given an n, it will make
me an exponentiator.
Oh, but that's easy too.
In other words, I can write
my program this way.
I'm going to define a thing
called PGEN, which is a
procedure of n which produces
for me an exponentiator.
--x to the n.
Given that I have that, then I
can capture the abstraction I
wanted even better, because now
it's encapsulated in a way
where I can't be destroyed
by a change of names.
I can define some powers to be a
procedure again of a, b, and
n which is the sum of the term
function generated by using
this generator, PGEN, n, with
a, incrementer, and b.
And I can define the product of
powers to be a procedure of
a, b, and n which is the product
PGEN, n, with a,
increment, and b.
Now, of course, this is a very
simple example where this
object that I'm trying to
abstract over is small.
But it could be a 100
lines of code.
And so, the purpose
of this is, of
course, to make it simple.
I'd give a name to it, it's
just that here it's a
parameterized name.
It's a name that depends upon,
explicitly, the lexically
apparent value of n.
So you can think of this
as a long name.
And here, I've solved my problem
by naming the term
generation procedures
within an n in them.
Are there any questions?
Oh, yes, David.
AUDIENCE: Is the only solution
to the problem you raise to
create another procedure?
In other words, can this only
work in languages that are
capable of defining objects
as procedures?
PROFESSOR: Oh, I see.
My solution to making this
abstraction, when I didn't
want include the procedure
inside the body, depends upon
my ability to return a procedure
or export one.
And that's right.
If I don't have that, then I
just don't have this ability
to make an abstraction in
a way where I don't have
possibilities of symbol
conflicts that were
unanticipated.
That's right.
I consider being able to return
the procedural value
and, therefore, to sort of have
first class procedures,
in general, as being essential
to doing very good modular
programming.
Now, indeed there are many other
ways to skin this cat.
What you can do is take for each
of the bad things that
you have to worry about, you
can make a special feature
that covers that thing.
You can make a package system.
You can make a module system
as in Ada, et cetera.
And all of those work, or they
cover little regions of it.
The thing is that returning
procedures as values cover all
of those problems. And so it's
the simplest mechanism that
gives you the best modularity,
gives you all of the known
modularity mechanisms.
Well, I suppose it's time for
the next break, thank you.
[MUSIC PLAYING]
PROFESSOR: Well, yesterday
when you learned about
streams, Hal worried to you
about the order of evaluation
and delayed arguments
to procedures.
The way we played with streams
yesterday, it was the
responsibility of the caller and
the callee to both agree
that an argument was delayed,
and the callee must force the
argument if it needs
the answer.
So there had to be a lot of
hand shaking between the
designer of a procedure and user
of it over delayedness.
That turns out, of course, to
be a fairly bad thing, it
works all right with streams.
But as a general thing, what
you want is an idea to have a
locus, a decision, a design
decision in general, to have
a place where it's made,
explicitly, and notated
in a clear way.
And so it's not a very good
idea to have to have an
agreement, between the person
who writes a procedure and the
person who calls it, about such
details as, maybe, the
arguments of evaluation, the
order of evaluation.
Although, that's not so bad.
I mean, we have other such
agreements like,
the input's a number.
But it would be nice if only one
of these guys could take
responsibility, completely.
Now this is not a new idea.
ALGOL 60 had two different ways
of calling a procedure.
The arguments could be passed
by name or by value.
And what that meant was that a
name argument was delayed.
That when you passed an argument
by name, that its
value would only be obtained if
you accessed that argument.
So what I'd like to do now is
show you, first of all, a
little bit about, again, we're
going to make a modification
to a language.
In this case, we're going
to add a feature.
We're going to add the feature
of, by name parameters, if you
will, or delayed parameters.
Because, in fact, the default
in our Lisp system is by the
value of a pointer.
A pointer is copied, but
the data structure it
points at is not.
But I'd like to, in fact, show
you is how you add name
arguments as well.
Now again, why would we
need such a thing?
Well supposing we wanted to
invent certain kinds of what
otherwise would be special
forms, reserve words?
But I'd rather not take
up reserve words.
I want procedures that can
do things like if.
If is special, or cond,
or whatever it is.
It's the same thing.
It's special in that it
determines whether or not to
evaluate the consequent or the
alternative based on the value
of the predicate part
of an expression.
So taking the value of one thing
determines whether or
not to do something else.
Whereas all the procedures like
plus, the ones that we
can define right now, evaluate
all of their arguments before
application.
So, for example, supposing I
wish to be able to define
something like the reverse
of if in terms of if.
Call it unless.
We've a predicate, a
consequent, and an
alternative.
Now what I would like to sort of
be able to do is say-- oh,
I'll do it in terms of cond.
Cond, if not the predicate,
then take the consequent,
otherwise, take the
alternative.
Now, what I'd like this to
mean, is supposing I do
something like this.
I'd like this unless say if
equals one, 0, then the answer
is two, otherwise, the quotient
of one and 0.
What I'd like that to mean is
the result of substituting
equal one, 0, and two, and
the quotient of one, 0
for p, c, and a.
I'd like that to mean, and this
is funny, I'd like it to
transform into or mean cond
not equal one, 0, then the
result is two, otherwise
I want it to be the
quotient one and 0.
Now, you know that if I
were to type this into
Lisp, I'd get a two.
There's no problem with that.
However, if I were to type this
into Lisp, because all
the arguments are evaluated
before I start, then I'm going
to get an error out of this.
So that if the substitutions
work at all, of course, I
would get the right answer.
But here's a case where the
substitutions don't work.
I don't get the wrong answer.
I get no answer.
I get an error.
Now, however, I'd like to be
able to make my definition so
that this kind of thing works.
What I want to do
is say something
special about c and a.
I want them to be delayed
automatically.
I don't want them to be
evaluated at the time I call.
So I'm going to make a
declaration, and then I'm
going to see how to implement
such a declaration.
But again, I want you to say
to yourself, oh, this is an
interesting kluge he's
adding in here.
The piles of kluges make
a big complicated mess.
And is this going to
foul up something
else that might occur.
First of all, is it
syntactically unambiguous?
Well, it will be syntactically
unambiguous with what we've
seen so far.
But what I'm going to do may,
in fact, cause trouble.
It may be that the thing I had
will conflict with type
declarations I might want to add
in the future for giving
some system, some compiler or
something, the ability to
optimize given the
types are known.
Or it might conflict with other
types of declarations I
might want to make about
the formal parameters.
So I'm not making a general
mechanism here where I can add
declarations.
And I would like to be
able to do that.
But I don't want to talk
about that right now.
So here I'm going to do, I'm
going to build a kluge.
So we're going to define
unless of a predicate--
and I'm going to call
these by name--
the consequent, and name
the alternative.
Huh, huh--
I got caught in the corner.
If not p then the result
is c, else--
that's what I'd like.
Where I can explicitly declare
certain of the parameters to
be delayed, to be
computed later.
Now, this is actually a very
complicated modification to an
interpreter rather than
a simple one.
The ones you saw before, dynamic
binding or adding
indefinite argument procedures,
is relatively simple.
But this one changes
a basic strategy.
The problem here is that our
interpreter, as written,
evaluates a combination by
evaluating the procedure, the
operator producing the
procedure, and evaluating the
operands producing the
arguments, and then doing
apply of the procedure
to the arguments.
However, here, I don't want to
evaluate the operands to
produce the arguments until
after I examined the procedure
to see what the procedure's
declarations look like.
So let's look at that.
Here we have a changed
evaluator.
I'm starting with the simple
lexical evaluator, not
dynamic, but we're going to have
to do something sort of
similar in some ways.
Because of the fact that,
if I delay a procedure--
I'm sorry-- delay an argument
to a procedure, I'm going to
have to attach and environment
to it.
Remember how Hal implemented
delay.
Hal implemented delay as being
a procedure of no arguments
which does some expression.
That's what delay of
the expression is.
--of that expression.
This turned into something
like this.
Now, however, if I evaluate a
lambda expression, I have to
capture the environment.
The reason why is because there
are variables in there
who's meaning I wish to derive
from the context where this
was written.
So that's why a lambda
does the job.
It's the right thing.
And such that the forcing of a
delayed expression was same
thing as calling that
with no arguments.
It's just the opposite
of this.
Producing an environment of the
call which is, in fact,
the environment where this was
defined with an extra frame in
it that's empty.
I don't care about that.
Well, if we go back to this
slide, since it's the case, if
we look at this for a second,
everything is the same as it
was before except the case of
applications or combinations.
And combinations are going
to do two things.
One, is I have to evaluate
the procedure--
forget the procedure-- by
evaluating the operator.
That's what you see
right here.
I have to make sure that that's
current, that is not a
delayed object, and evaluate
that to the point where it's
forced now.
And then I have to somehow apply
that to the operands.
But I have to keep the
environment, pass that
environmental along.
So some of those operands
I may have to delay.
I may have to attach that
environment to those operands.
This is a rather complicated
thing happening here.
Looking at that in apply.
Apply, well it has a
primitive procedure
thing just like before.
But the compound one is a
little more interesting.
I have to evaluate the body,
just as before, in an
environment which is the result
of binding some formal
parameters to arguments
in the environment.
That's true.
The environment is the
one that comes from
the procedure now.
It's a lexical language,
statically bound.
However, one thing I have
to do is strip off the
declarations to get the names
of the variables.
That's what this guy
does, vnames.
And the other thing I have
to do is process these
declarations, deciding which
of these operands--
that's the operands now, as
opposed to the arguments--
which of these operands to
evaluate, and which of them
are to be encapsulated in
delays of some sort.
The other thing you see here is
that we got a primitive, a
primitive like plus, had better
get at the real operands.
So here is a place where we're
going to have to force them.
And we're going to look at what
evlist is going to have
to do a bunch of forces.
So we have two different
kinds of evlist now.
We have evlist and gevlist.
Gevlist is going to wrap
delays around some things and
force others, evaluate others.
And this guy's going to do
some forcing of things.
Just looking at this a little
bit, this is a game you must
play for yourself, you know.
It's not something that you're
going to see all possible
variations on an evaluator
talking to me.
What you have to do is
do this for yourself.
And after you feel this, you
play this a bit, you get to
see all the possible design
decisions and what they might
mean, and how they interact
with each other.
So what languages might
have in them.
And what are some of the
consistent sets that make a
legitimate language.
Whereas what things are
complicated kluges that are
just piles of junk.
So evlist of course, over here,
just as I said, is a
list of operands which are going
to be undelayed after
evaluation.
So these are going to
be forced, whatever
that's going to mean.
And gevlist, which is
the next thing--
Thank you.
What we see here, well there's
a couple of possibilities.
Either it's a normal, ordinary
thing, a symbol sitting there
like the predicate in the
unless, and that's
what we have here.
In which case, this is intended
to be evaluated in
applicative order.
And it's, essentially, just
what we had before.
It's mapping eval down the
list. In other words, I
evaluate the first expression
and continue gevlisting the
CDR of the expression
in the environment.
However, it's possible that
this is a name parameter.
If it's a name parameter, I want
to put a delay in which
combines that expression, which
I'm calling by name,
with the environment that's
available at this time and
passing that as the parameter.
And this is part of the
mapping process
that you see here.
The only other interesting
place in this
interpreter is cond.
People tend to write this thing,
and then they leave
this one out.
There's a place where
you have to force.
Conditionals have to know
whether or not the answer is
true or false.
It's like a primitive.
When you do a conditional,
you have to force.
Now, I'm not going to
look at any more
of this in any detail.
It isn't very exciting.
And what's left is how
you make delays.
Well, delays are data structures
which contain an
expression, an environment,
and a type on them.
And it says they're a thunk.
That comes from ALGOL language,
and it's claimed to
be the sound of something
being pushed on a stack.
I don't know.
I was not an ALGOLician or an
ALGOLite or whatever, so I
don't know.
But that's what was claimed.
And undelay is something which
will recursively undelay
thunks until the thunk becomes
something which isn't a thunk.
This is the way you implement
a call by name
like thing in ALGOL.
And that's about all there is.
Are there any questions?
AUDIENCE: Gerry?
PROFESSOR: Yes, Vesko?
AUDIENCE: I noticed you avoided
calling by name in the
primitive procedures,
I was wondering what
cause you have on that?
You never need that?
PROFESSOR: Vesko is asking if
it's ever reasonable to call a
primitive procedure by name?
The answer is, yes.
There's one particular case
where it's reasonable,
actually two.
Construction of a data structure
like cons where
making an array if you
have arrays with
any number of elements.
It's unnecessary to evaluate
those arguments.
All you need is promises to
evaluate those arguments if
you look at them.
If I cons together two things,
then I could cons together the
promises just as easily as I can
cons together the things.
And it's not even when
I CAR CDR them that I
have to look at them.
That just gets out
the promises and
passes them to somebody.
That's why the lambda calculus
definition, the Alonzo Church
definition of CAR, CDR,
and cons makes sense.
It's because no work is done
in CAR, CDR, and cons, it's
just shuffling data, it's just
routing, if you will.
However, the things that do
have to look at data are
things like plus.
Because they have a look at the
bits that the numbers are
made out of, unless they're
lambda calculus
numbers which are funny.
They have to look at the bits
to be able to crunch them
together to do the add.
So, in fact, data constructors,
data selectors,
and, in fact, things that
side-effect data objects don't
need to do any forcing in the
laziest possible interpreters.
On the other hand predicates
on data structures have to.
Is this a pair?
Or is it a symbol?
Well, you better find out.
You got to look at it then.
Any other questions?
Oh, well, I suppose it's
time for a break.
Thank you.
[MUSIC PLAYING]
and
