PROFESSOR: Well, yesterday we learned a bit about symbolic
manipulation, and we wrote a rather stylized program to
implement a pile of calculus rule from the calculus book.
Here on the transparencies, we see a bunch of calculus rules
from such a book.
And, of course, what we did is sort of translate these rules
into the language of the computer.
But, of course, that's a sort of funny strategy.
Why should we have to translate these rules into the
language of the computer?
And what do I really mean by that?
These are--the program we wrote
yesterday was very stylized.
It was a conditional, a dispatch on the type of the
expression as observed by the rules.
What we see here are rules that say if the object being
the derivative is being taken of, if that expression is a
constant, then do one thing.
If it's a variable, do another thing.
If it's a product of a constant times a variable, do
something and so on.
There's sort of a dispatch there on a type.
Well, since it has such a stylized behavior and
structure, is there some other way of writing this program
that's more clear?
Well, what's a rule, first of all?
What are these rules?
Let's think about that.
Rules have parts.
If you look at these rules in detail, what you see, for
example, is the rule has a left-hand side and a
right-hand side.
Each of these rules has a left-hand side and the
right-hand side.
The left-hand side is somehow compared with the expression
you're trying to take the derivative of.
The right-hand side is the replacement for that
expression.
So all rules on this page are something like this.
I have patterns, and somehow, I have to produce, given a
pattern, a skeleton.
This is a rule.
A pattern is something that matches, and a skeleton is
something you substitute into in order to get a new
expression.
So what that means is that the pattern is matched against the
expression, which is the source expression.
And the result of the application of the rule is to
produce a new expression, which I'll call a target, by
instantiation of a skeleton.
That's called instantiation.
So that is the process by which
these rules are described.
What I'd like to do today is build a language and a means
of interpreting that language, a means of executing that
language, where that language allows us to directly express
these rules.
And what we're going to do is instead of bringing the rules
to the level of the computer by writing a program that is
those rules in the computer's language--
at the moment, in a Lisp--
we're going to bring the computer to the level of us by
writing a way by which the computer can understand rules
of this sort.
This is slightly emphasizing the idea that we had last time
that we're trying to make a solution to a class of
problems rather than a particular one.
The problem is if I want to write rules for a different
piece of mathematics, say, to simple algebraic
simplification or something like that, or manipulation of
trigonometric functions, I would have to write a
different program in using yesterday's method.
Whereas I would like to encapsulate all of the things
that are common to both of those programs, meaning the
idea of matching, instantiation, the control
structure, which turns out to be very complicated for such a
thing, I'd like to encapsulate that separately from the rules
themselves.
So let's look at, first of all, a representation.
I'd like to use the overhead here.
I'd like-- there it is.
I'd like to look at a representation of the rules of
calculus for derivatives in a sort of simple language that
I'm writing right here.
Now, I'm going to avoid--I'm going to avoid
worrying about syntax.
We can easily pretty this, and I'm not interested in making--
this is indeed ugly.
This doesn't look like the beautiful text set dx by dt or
something that I'd like to write,
but that's not essential.
That's sort of an accidental phenomenon.
Here, we're just worrying about the fact that the
structure of the rules is that there is a left-hand side
here, represents the thing I want to match against the
derivative expression.
This is the representation I'm going to say for the
derivative of a constant, which we will call c with
respect to the variable we will call v. And what we will
get on the right-hand side is 0.
So this represents a rule.
The next rule will be the derivative of a variable,
which we will call v with respect to the same variable
v, and we get a 1.
However, if we have the derivative of a variable
called u with respect to a different variables
v, we will get 0.
I just want you look at these rules a little bit and see how
they fit together.
For example, over here, we're going to have the derivative
of the sum of an expression called x1 and an
expression called x2.
These things that begin with question marks are called
pattern variables in the language that we're inventing,
and you see we're just making it up, so pattern variables
for matching.
And so in this--
here we have the derivative of the sum of the expression
which we will call x1.
And the expression we will call x2 with respect to the
variable we call v will be-- here is the right-hand side:
the sum of the derivative of that expression x1 with
respect to v-- the right-hand side is the skeleton--
and the derivative of x2 with respect to v. Colons here will
stand for substitution objects.
They're--we'll call them skeleton evaluations.
So let me put up here on the blackboard for a second some
syntax so we'll know what's going on
for this rule language.
First of all, we're going to have to worry about the
pattern matching.
We're going to have things like a symbol like foo matches
exactly itself.
The expression f of a and b will be used to match any list
whose first element is f, whose second element is a, and
whose third element is b.
Also, another thing we might have in a pattern is that--
a question mark with some variable like x.
And what that means, it says matches anything, which we
will call x.
Question mark c x will match only constants.
So this is something which matches a constant colon x.
And question mark v x will match a variable,
which we call x.
This is sort of the language we're making up now.
If I match two things against each other, then they are
compared element by element.
But elements in the pattern may contain these syntactic
variables, pattern variables, which will be used to match
arbitrary objects.
And we'll get that object as the value in the name x here,
for example.
Now, when we make skeletons for instantiation.
Well, then we have things like this.
foo, a symbol, instantiates to itself.
Something which is a list like f of a and b,
instantiates to--
well, f instantiates to a 3-list, a list of three
elements, okay, which are the results of instantiating each
of f, a, and b.
And x well--we instantiate to the value of x as in the
matched pattern.
So going back to the overhead here, we see--we see that all
of those kinds of objects, we see here a pattern variable
which matches a constant, a pattern variable which matches
a variable, a pattern variable which will match anything.
And if we have two instances of the same name, like this is
the derivative of the expression which is a variable
only whose name will be v with respect to some arbitrary
expression which we will call v, since this v appears twice,
we're going to want that to mean they have to be the same.
The only consistent match is that those are the same.
So here, we're making up a language.
And in fact, that's a very nice thing to be doing.
It's so much fun to make up a language.
And you do this all the time.
And the really most powerful design things you ever do are
sort of making up a language to solve problems like this.
Now, here we go back here and look at some of these rules.
Well, there's a whole set of them.
I mean, there's one for addition and one for
multiplication, just like we had before.
The derivative of the product of x1 and x2 with respect to v
is the sum of the product of x1 and the derivative x2 with
respect to v and the product of the
derivative of x1 and x2.
And here we have exponentiation.
And, of course, we run off the end down here.
We get as many as we like.
But the whole thing over here, I'm giving this--this list of
rules the name "derivative rules."
What would we do with such a thing once we have it?
Well, one of the nicest ideas, first of all, is I'm going to
write for you, and we're going to play with it all day.
What I'm going to write for you is a program called
simplifier, the general-purpose simplifier.
And we're going to say something like define dsimp to
be a simplifier of the derivative rules.
And what simplifier is going to do is, given a set of
rules, it will produce for me a procedure which will
simplify expressions containing the things that are
referred to by these rules.
So here will be a procedure constructed for your purposes
to simplify things with derivatives in them such that,
after that, if we're typing at some list system, and we get a
prompt, and we say dsimp, for example, of the derivative of
the sum of x and y with respect to x--
note the quote here because I'm talking about the
expression which is the derivative--
then I will get back as a result plus 1 0.
Because the derivative of x plus y is the derivative of x
plus derivative y.
The derivative of x with respect to x is 1.
The derivative of y with respect to x is 0.
It's not what we're going to get.
I haven't put any simplification at that level--
algebraic simplification--
yet.
Of course, once we have such a thing, then we can--then we
can look at other rules.
So, for example, we can, if we go to the slide, OK?
Here, for example, are other rules that we might have,
algebraic manipulation rules, ones that would be used for
simplifying algebraic expressions.
For example, just looking at some of these, the left-hand
side says any operator applied to a constant e1 and a
constant e2 is the result of evaluating that operator on
the constants e1 and e2.
Or an operator, applied to e1, any expression e1 and a
constant e2, is going to move the constant forward.
So that'll turn into the operator with
e2 followed by e1.
Why I did that, I don't know.
It wouldn't work if I had division, for example.
So there's a bug in the rules, if you like.
So the sum of 0 and e is e.
The product of 1 and any expression e is e.
The product of 0 and any expression e is 0.
Just looking at some more of these rules, we could have
arbitrarily complicated ones.
We could have things like the product of the constant e1 and
any constant e2 with e3 is the result of multiplying the
result of--multiplying now the constants e1 and e2 together
and putting e3 there.
So it says combine the constants that I had, which
was if I had a product of e1 and e2 and e3 just multiply--I
mean and e1 and e2 are both constants, multiply them.
And you can make up the rules as you like.
There are lots of them here.
There are things as complicated, for example, as--
oh, I suppose down here some distributive law, you see.
The product of any object c and the sum of d and e gives
the result as the same as the sum of the product of c and d
and the product of c and e.
Now, what exactly these rules are doesn't very
much interest me.
We're going to be writing the language that will allow us to
interpret these rules so that we can, in fact, make up
whatever rules we like, another whole language of
programming.
Well, let's see.
I haven't told you how we're going to do this.
And, of course, for a while, we're going to work on that.
But there's a real question of what is--what am I going to do
at all at a large scale?
How do these rules work?
How is the simplifier program going to manipulate these
rules with your expression to produce a reasonable answer?
Well, first, I'd like to think about these rules as being
some sort of deck of them.
So here I have a whole bunch of rules, right?
Each rule--
here's a rule--
has a pattern and a skeleton.
I'm trying to make up a control structure for this.
Now, what I have is a matcher, and I have something which is
an instantiater.
And I'm going to pass from the matcher to the instantiater
some set of meaning for the pattern variables, a
dictionary, I'll call it.
A dictionary, which will say x was matched against the
following subexpression and y was matched against another
following subexpression.
And from the instantiater, I will be making expressions,
and they will go into the matcher.
They will be expressions.
And the patterns of the rules will be fed into the matcher,
and the skeletons from the same rule will be fed into the
instantiater.
Now, this is a little complicated because when you
have something like an algebraic expression, where
someth--the rules are intended to be able to allow you to
substitute equal for equal.
These are equal transformation rules.
So all subexpressions of the expression
should be looked at.
You give it an expression, this thing, and the rules
should be cycled around.
First of all, for every subexpression of the
expression you feed in, all of the rules must be
tried and looked at.
And if any rule matches, then this process occurs.
The dictionary--the dictionary is to have some values in it.
The instantiater makes a new expression, which is basically
replaces that part of the expression that was matched in
your original expression.
And then, then, of course, we're going to recheck that,
going to go around these rules again, seeing if that could be
simplified further.
And then, then we're going to do that for every
subexpression until the thing no longer changes.
You can think of this as sort of an organic process.
You've got some sort of stew, right?
You've got bacteria or something, or enzymes in some,
in some gooey mess.
And there's these--and these enzymes change things.
They attach to your expression, change it, and
then they go away.
And they have to match.
The key-in-lock phenomenon.
They match, they change it, they go away.
You can imagine it as a parallel process of some sort.
So you stick an expression into this mess, and after a
while, you take it out, and it's been simplified.
And it just keeps changing until it no
longer can be changed.
But these enzymes can attach to any part of the, of the
expression.
OK, at this point, I'd like to stop and ask for questions.
Yes.
AUDIENCE: This implies that the matching program and the
instantiation program are separate
programs; is that right?
Or is that-- they are.
PROFESSOR: They're separate little pieces.
They fit together in a larger structure.
AUDIENCE: So I'm going through and matching and passing the
information about what I matched to an instantiater,
which makes the changes.
And then I pass that back to the matcher?
PROFESSOR: It won't make a change.
It will make a new expression, which has, which has
substituted the values of the pattern variable that were
matched on the left-hand side for the variables that are
mentioned, the skeleton variables or evaluation
variables or whatever I called them, on the right-hand side.
AUDIENCE: And then that's passed back into the matcher?
PROFESSOR: Then this is going to go around again.
This is going to go through this mess until
it no longer changes.
AUDIENCE: And it seems that there would be a danger of
getting into a recursive loop.
PROFESSOR: Yes.
Yes, if you do not write your rules nicely, you are--
indeed, in any programming language you invent, if it's
sufficiently powerful to do anything, you can write
programs that will go into infinite loops.
And indeed, writing a program for doing algebraic
manipulation for long will produce infinite loops.
Go ahead.
AUDIENCE: Some language designers feel that this
feature is so important that it should become part of the
basic language, for example, scheme in this case.
What are your thoughts on--
PROFESSOR: Which language feature?
AUDIENCE: The pairs matching.
It's all application of such rules should be--
PROFESSOR: Oh, you mean like Prolog?
AUDIENCE: Like Prolog, but it becomes a more general--
PROFESSOR: It's possible.
OK, I think my feeling about that is that I would like to
teach you how to do it so you don't depend upon some
language designer.
AUDIENCE: OK.
PROFESSOR: You make it yourself.
You can roll your own.
Thank you.
Well, let's see.
Now we have to tell you how it works.
It conveniently breaks up into various pieces.
I'd like to look now at the matcher.
The matcher has the following basic structure.
It's a box that takes as its input an expression and a
pattern, and it turns out a dictionary.
A dictionary, remember, is a mapping of pattern variables
to the values that were found by matching, and it puts out
another dictionary, which is the result of augmenting this
dictionary by what was found in matching this expression
against this pattern.
So that's the matcher.
Now, this is a rather complicated program, and we
can look at it on the overhead over here and see, ha, ha,
it's very complicated.
I just want you to look at the shape of it.
It's too complicated to look at except in pieces.
However, it's a fairly large, complicated program with a lot
of sort of indented structure.
At the largest scale--
you don't try to read those characters, but at the largest
scale, you see that there is a case analysis, which is all
these cases lined up.
What we're now going to do is look at this in a bit more
detail, attempting to understand how it works.
Let's go now to the first slide, showing some of the
structure of the matcher at a large scale.
And we see that the matcher, the matcher takes as its input
a pattern, an expression, and a dictionary.
And there is a case analysis here, which is made out of
several cases, some of which have been left out over here,
and the general case, which I'd like you to see.
Let's consider this general case.
It's a very important pattern.
The problem is that we have to examine two trees
simultaneously.
One of the trees is the tree of the expression, and the
other is the tree of the pattern.
We have to compare them with each other so that the
subexpressions of the expression are matched against
subexpressions of the pattern.
Looking at that in a bit more detail, suppose I had a
pattern, a pattern, which was the sum of the product of a
thing which we will call x and a thing which we will call y,
and the sum of that, and the same thing we call y.
So we're looking for a sum of a product whose second--whose
second argument is the same as the second
argument of the sum.
That's a thing you might be looking for.
Well, that, as a pattern, looks like this.
There is a tree, which consists of a sum, and a
product with a pattern variable question mark x and
question mark y, the other pattern variable, and question
mark y, just looking at the same, just writing down the
list structure in a different way.
Now, suppose we were matching that against an expression
which matches it, the sum of, say, the product of 3 and x
and, say, x.
That's another tree.
It's the sum of the product of 3 and x and of x.
So what I want to do is traverse these two trees
simultaneously.
And what I'd like to do is walk them like this.
I'm going to say are these the same?
This is a complicated object.
Let's look at the left branches.
Well, that could be the car.
How does that look?
Oh yes, the plus looks just fine.
But the next thing here is a complicated thing.
Let's look at that.
Oh yes, that's pretty fine, too.
They're both asterisks.
Now, whoops!
My pattern variable, it matches against the 3.
Remember, x equals 3 now.
That's in my dictionary, and the dictionary's going to
follow along with me: x equals three.
Ah yes, x equals 3 and y equals x, different x.
The pattern x is the expression x, the pattern y.
Oh yes, the pattern variable y, I've already
got a value for it.
It's x.
Is this an x?
Oh yeah, sure it is.
That's fine.
Yep, done.
I now have a dictionary, which I've accumulated
by making this walk.
Well, now let's look at this general case here and see how
that works.
Here we have it.
I take in a pattern variable--a pattern, an
expression, and a dictionary.
And now I'm going to do a complicated thing here, which
is the general case.
The expression is made out of two parts: a left and a right
half, in general.
Anything that's complicated is made out of two pieces in a
Lisp system.
Well, now what do we have here?
I'm going to match the car's of the two expressions against
each other with respect to the dictionary I already have,
producing a dictionary as its value, which I will then use
for matching the cdr's against each other.
So that's how the dictionary travels,
threads the entire structure.
And then the result of that is the dictionary for the match
of the car and the cdr, and that's what's going to be
returned as a value.
Now, at any point, a match might fail.
It may be the case, for example, if we go back and
look at an expression that doesn't quite match, like
supposing this was a 4.
Well, now these two don't match any more, because the x
that had to be-- sorry, the y that had to be x here and this
y has to be 4.
But x and 4 were not the same object syntactically.
So this wouldn't match, and that would be rejected
sometimes, so matches may fail.
Now, of course, because this matcher takes the dictionary
from the previous match as input, it must be able to
propagate the failures.
And so that's what the first clause of
this conditional does.
It's also true that if it turned out that the pattern
was not atomic--
see, if the pattern was atomic, I'd go into this
stuff, which we haven't looked at yet.
But if the pattern is not atomic and the
expression is atomic--
it's not made out of pieces--
then that must be a failure, and so we go over here.
If the pattern is not atomic and the pattern is not a
pattern variable--
I have to remind myself of that--
then we go over here.
So that way, failures may occur.
OK, so now let's look at the insides of this thing.
Well, the first place to look is what happens if I have an
atomic pattern?
That's very simple.
A pattern that's not made out of any pieces: foo.
That's a nice atomic pattern.
Well, here's what we see.
If the pattern is atomic, then if the expression is atomic,
then if they are the same thing, then the dictionary I
get is the same one as I had before.
Nothing's changed.
It's just that I matched plus against plus, asterisk against
asterisk, x against x.
That's all fine.
However, if the pattern is not the one which is the
expression, if I have two separate atomic objects, then
it was matching plus against asterisk, which case I fail.
Or if it turns out that the pattern is atomic but the
expression is complicated, it's not atomic,
then I get a failure.
That's very simple.
Now, what about the various kinds of pattern variables?
We had three kinds.
I give them the names.
They're arbitrary constants, arbitrary variables, and
arbitrary expressions.
A question mark x is an arbitrary expression.
A question mark cx is an arbitrary constant, and a
question mark vx is an arbitrary variable.
Well, what do we do here?
Looking at this, we see that if I have an arbitrary
constant, if the pattern is an arbitrary constant, then it
had better be the case that the expression
had better be a constant.
If the expression is not a constant,
then that match fails.
If it is a constant, however, then I wish to extend the
dictionary.
I wish to extend the dictionary with that pattern
being remembered to be that expression using the old
dictionary as a starting point.
So really, for arbitrary variables, I have to check
first if the expression is a variable by matching against.
If so, it's worth extending the dictionary so that the
pattern is remembered to be matched against that
expression, given the original dictionary, and this makes a
new dictionary.
Now, it has to check.
There's a sorts of failure inside extend dictionary,
which is that--
if one of these pattern variables already has a value
and I'm trying to match the thing against something else
which is not equivalent to the one that I've already matched
it against once, then a failure will come flying out
of here, too.
And I will see that some time.
And finally, an arbitrary expression does not have to
check anything syntactic about the expression that's being
matched, so all it does is it's an extension of the
dictionary.
So you've just seen a complete, very simple matcher.
Now, one of the things that's rather remarkable about this
is people pay an awful lot of money these days for someone
to make a, quote, AI expert system that has nothing more
in it than a matcher and maybe an instantiater like this.
But it's very easy to do, and now, of course, you can start
up a little start-up company and make a couple of megabucks
in the next week taking some people for a ride.
20 years ago, this was remarkable,
this kind of program.
But now, this is sort of easy.
You can teach it to freshmen.
Well, now there's an instantiater as well.
The problem is they're all going off and making more
money than I do.
But that's always been true of universities.
As expression, the purpose of the instantiater is to make
expressions given a dictionary and a skeleton.
And that's not very hard at all.
We'll see that very simply in the next, the next slide here.
To instantiate a skeleton, given a particular
dictionary--
oh, this is easy.
We're going to do a recursive tree walk over the skeleton.
And for everything which is a skeleton variable--
I don't know, call it a skeleton evaluation.
That's the name and the abstract syntax that I give it
in this program: a skeleton evaluation, a thing beginning
with a colon in the rules.
For anything of that case, I'm going to look up the answer in
the dictionary, and we'll worry about that in a second.
Let's look at this as a whole.
Here, I have--
I'm going to instantiate a skeleton, given a dictionary.
Well, I'm going to define some internal loop right there, and
it's going to do something very simple.
Even if a skeleton--even if a skeleton is simple and atomic,
in which case it's nothing more than giving the skeleton
back as an answer, or in the general case, it's
complicated, in which case I'm going to make up the
expression which is the result of instantiating--
calling this loop recursively--
instantiating the car of the skeleton and the cdr.
So here is a recursive tree walk.
However, if it turns out to be a skeleton evaluation, a colon
expression in the skeleton, then what I'm going to do is
find the expression that's in the colon--
the CADR in this case.
It's a piece of abstract syntax here, so I can change
my representation of rules.
I'm going to evaluate that relative to this dictionary,
whatever evaluation means.
We'll find out a lot about that sometime.
And the result of that is my answer.
so.
I start up this loop-- here's my initialization--
by calling it with the whole skeleton, and this will just
do a recursive decomposition into pieces.
Now, one more little bit of detail is what
happens inside evaluate?
I can't tell you that in great detail.
I'll tell you a little bit of it.
Later, we're going to see--look into this in much
more detail.
To evaluate some form, some expression with respect to a
dictionary, if the expression is an atomic object, well, I'm
going to go look it up.
Nothing very exciting there.
Otherwise, I'm going to do something complicated here,
which is I'm going to apply a procedure which is the result
of looking up the operator part in something that we're
going to find out about someday.
I want you realize you're seeing magic now.
This magic will become clear very soon, but not today.
Then I'm looking at--looking up all the pieces, all the
arguments to that in the dictionary.
So I don't want you to look at this in detail.
I want you to say that there's more going on here, and we're
going to see more about this.
But it's--
the magic is going to stop.
This part has to do with Lisp, and it's the end of that.
OK, so now we know about matching and instantiation.
Are there any questions for this segment?
AUDIENCE: I have a question.
PROFESSOR: Yes.
AUDIENCE: Is it possible to bring up a previous slide?
It's about this define match pattern.
PROFESSOR: Yes.
You'd like to see the overall slide define match pattern.
Can somebody put up the--
no, the overhead.
That's the biggest scale one.
What part would you like to see?
AUDIENCE: Well, the top would be fine.
Any of the parts where you're passing failed.
PROFESSOR: Yes.
AUDIENCE: The idea is to pass failed back to the dictionary;
is that right?
PROFESSOR: The dictionary is the answer to a match, right?
And it is either some mapping or there's no match.
It doesn't match.
AUDIENCE: Right.
PROFESSOR: So what you're seeing over here is, in fact,
because the fact that a match may have another match pass in
the dictionary, as you see in the general case down here.
Here's the general case where a match passes another match
to the dictionary.
When I match the cdr's, I match them in the dictionary
that is resulting from matching the car's.
OK, that's what I have here.
So because of that, if the match of the car's fails, then
it may be necessary that the match of the cdr's propagates
that failure, and that's what the first line is.
AUDIENCE: OK, well, I'm still unclear what matches--
what comes out of one instance of the match?
PROFESSOR: One of two possibilities.
Either the symbol failed, which means there is no match.
AUDIENCE: Right.
PROFESSOR: Or some mapping, which is an abstract thing
right now, and you should know about the structure of it,
which relates the pattern variables to their values as
picked up in the match.
AUDIENCE: OK, so it is--
PROFESSOR: That's constructed by extend dictionary.
AUDIENCE: So the recursive nature brings about the fact
that if ever a failed gets passed out of any calling of
match, then the first condition will pick it up--
PROFESSOR: And just propagate it along without any further
ado, right.
AUDIENCE: Oh, right.
OK.
PROFESSOR: That's just the fastest way to get that
failure out of there.
Yes.
AUDIENCE: If I don't fail, that means that I've matched a
pattern, and I run the procedure extend dict and then
pass in the pattern in the expression.
But the substitution will not be made at that
point; is that right?
I'm just--
PROFESSOR: No, no.
There's no substitution being there because there's no
skeleton to be substituted in.
AUDIENCE: Right.
So what--
PROFESSOR: All you've got there is we're making up the
dictionary for later substitution.
AUDIENCE: And what would the dictionary look like?
Is it ordered pairs?
PROFESSOR: That's--that's not told to you.
We're being abstract.
AUDIENCE: OK.
PROFESSOR: Why do you want to know?
What it is, it's a function.
It's a function.
AUDIENCE: Well, the reason I want to know is--
PROFESSOR: A function abstractly is a
set of ordered pairs.
It could be implemented as a set of list pairs.
It could be implemented as some fancy table mechanism.
It could be implemented as a function.
And somehow, I'm building up a function.
But I'm not telling you.
That's up to George, who's going to build that later.
I know you really badly want to write concrete things.
I'm not going to let you do that.
AUDIENCE: Well, let me at least ask, what is the
important information there that's being
passed to extend dict?
I want to pass the pattern I found--
PROFESSOR: Yes.
The pattern that's matched against the expression.
You want to have the pattern, which happens to be in those
cases pattern variables, right?
All of those three cases for extend
dict are pattern variables.
AUDIENCE: Right.
PROFESSOR: So you have a pattern variable that is to be
given a value in a dictionary.
AUDIENCE: Mm-hmm.
PROFESSOR: The value is the expression that it matched
against. The dictionary is the set of things I've already
figured out that I have memorized or learned.
And I am going to make a new dictionary, which is extended
from the original one by having that pattern variable
have a value with the new dictionary.
AUDIENCE: I guess what I don't understand is why can't the
substitution be made right as soon as you find--
PROFESSOR: How do I know what I'm going to substitute?
I don't know anything about this skeleton.
This pattern, this matcher is an independent unit.
AUDIENCE: Oh, I see.
OK.
PROFESSOR: Right?
AUDIENCE: Yeah.
PROFESSOR: I take the matcher.
I apply the matcher.
If it matches, then it was worth doing instantiation.
AUDIENCE: OK, good.
Yeah.
PROFESSOR: OK?
AUDIENCE: Can you just do that answer again using that
example on the board?
You know, what you just passed back to the matcher.
PROFESSOR: Oh yes.
OK, yes.
You're looking at this example.
At this point when I'm traversing this structure, I
get to here: x.
I have some dictionary, presumably an empty dictionary
at this point if this is the whole expression.
So I have an empty dictionary, and I've matched x against 3.
So now, after this point, the dictionary
contains x is 3, OK?
Now, I continue walking along here.
I see y.
Now, this is a particular x, a pattern x.
I see y, a pattern y.
The dictionary says, oh yes, the pattern y is the symbol x
because I've got a match there.
So the dictionary now contains at this point two entries.
The pattern x is 3, and the pattern y is the expression x.
Now, I get that, I can walk along further.
I say, oh, pattern y also wants to be 4.
But that isn't possible, producing a failure.
Thank you.
Let's take a break.
OK, you're seeing your first very big and hairy program.
Now, of course, one of the goals of this subsegment is to
get you to be able to read something like this and not be
afraid of it.
This one's only about four pages of code.
By the end of the subject, I hope a 50-page program will
not look particularly frightening.
But I don't expect-- and I don't want you to think that I
expect you to be getting it as it's coming out.
You're supposed to feel the flavor of this, OK?
And then you're supposed to think about it because it is a
big program.
There's a lot of stuff inside this program.
Now, I've told you about the language we're implementing,
the pattern match substitution language.
I showed you some rules.
And I've told you about matching and instantiation,
which are the two halves of how a rule works.
Now we have to understand the control structure by which the
rules are applied to the expressions so as to do
algebraic simplification.
Now, that's also a big complicated mess.
The problem is that there is a variety of interlocking,
interwoven loops, if you will, involved in this.
For one thing, I have to apply--
I have to examine every subexpression of my expression
that I'm trying to simplify.
That we know how to do.
It's a car cdr recursion of some sort, or something like
that, and some sort of tree walk.
And that's going to be happening.
Now, for every such place, every node that I get to in
doing my traversal of the expression I'm trying to
simplify, I want to apply all of the rules.
Every rule is going to look at every node.
I'm going to rotate the rules around.
Now, either a rule will or will not match.
If the rule does not match, then it's not very
interesting.
If the rule does match, then I'm going to replace that node
in the expression by an alternate expression.
I'm actually going to make a new
expression, which contains--
everything contains that new value, the result of
substituting into the skeleton, instantiating the
skeleton for that rule at this level.
But no one knows whether that thing that I instantiated
there is in simplified form.
So we're going to have to simplify that, somehow to call
the simplifier on the thing that I just constructed.
And then when that's done, then I sort of can build that
into the expression I want as my answer.
Now, there is a basic idea here, which I will call a
garbage- in, garbage-out simplifier.
It's a kind of recursive simplifier.
And what happens is the way you simplify something is that
simple objects like variables are simple.
Compound objects, well, I don't know.
What I'm going to do is I'm going to build up from simple
objects, trying to make simple things by assuming that the
pieces they're made out of are simple.
That's what's happening here.
Well, now, if we look at the first slide--
no, overhead, overhead.
If we look at the overhead, we see a very complicated program
like we saw before for the matcher, so complicated that
you can't read it like that.
I just want you to get the feel of the shape of it, and
the shape of it is that this program has various
subprograms in it.
One of them--this part is the part for traversing the
expression, and this part is the part for trying rules.
Now, of course, we can look at that in some more detail.
Let's look at--let's look at the first transparency, right?
The simplifier is made out of several parts.
Now, remember at the very beginning, the simplifier is
the thing which takes a rules--a set of rules and
produces a program which will simplify it relative to them.
So here we have our simplifier.
It takes a rule set.
And in the context where that rule set is defined, there are
various other definitions that are done here.
And then the result of this simplifier procedure is, in
fact, one of the procedures that was defined.
Simplify x.
What I'm returning as the value of calling the
simplifier on a set of rules is a procedure, the simplify x
procedure, which is defined in that context, which is a
simplification procedure appropriate for using those
set of rules.
That's what I have there.
Now, the first two of these procedures, this one and this
one, are together going to be the recursive traversal of an
expression.
This one is the general simplification for any
expression, and this is the thing which simplifies a list
of parts of an expression.
Nothing more.
For each of those, we're going to do something complicated,
which involves trying the rules.
Now, we should look at the various parts.
Well let's look first at the recursive traversal of an
expression.
And this is done in a sort of simple way.
This is a little nest of recursive procedures.
And what we have here are two procedures--
one for simplifying an expression, and one for
simplifying parts of an expression.
And the way this works is very simple.
If the expression I'm trying to simplify is a compound
expression, I'm going to simplify all the parts of it.
And that's calling--that procedure, simplify parts, is
going to make up a new expression with all the parts
simplified, which I'm then going to try the
rules on over here.
If it turns out that the expression is not compound, if
it's simple, like just a symbol or something like pi,
then in any case, I'm going to try the rules on it because it
might be that I want in my set of rules to expand pi to
3.14159265358979, dot, dot, dot.
But I may not.
But there is no reason not to do it.
Now, if I want to simplify the parts, well, that's easy too.
Either the expression is an empty one, there's no more
parts, in which case I have the empty expression.
Otherwise, I'm going to make a new expression by cons, which
is the result of simplifying the first part of the
expression, the car, and simplifying the rest of the
expression, which is the cdr.
Now, the reason why I'm showing you this sort of stuff
this way is because I want you get the feeling for the
various patterns that are very important when writing
programs. And this could be written a different way.
There's another way to write simplified expressions so
there would be only one of them.
There would only be one little procedure here.
Let me just write that on the blackboard to give you a
feeling for that.
This in another idiom, if you will.
To simplify an expression called x, what
am I going to do?
I'm going to try the rules on the following situation.
If--
on the following expression--
compound, just like we had before.
If the expression is compound, well, what am I going to do?
I'm going to simplify all the parts.
But I already have a cdr recursion, a common pattern of
usage, which has been captured as a high-order procedure.
It's called map.
So I'll just write that here.
Map simplify the expression, all the parts of the
expression.
This says apply the simplification operation,
which is this one, every part of the expression, and then
that cuts those up into a list. It's every element of
the list which the expression is assumed to be made out of,
and otherwise, I have the expression.
So I don't need the helper procedure, simplify parts,
because that's really this.
So sometimes, you just write it this way.
It doesn't matter very much.
Well, now let's take a look at--
let's just look at how you try rules.
If you look at this slide, we see this is a
complicated mess also.
I'm trying rules on an expression.
It turns out the expression I'm trying it on is some
subexpression now of the expression I started with.
Because the thing I just arranged allowed us to try
every subexpression.
So now here we're taking in a subexpression of the
expression we started with.
That's what this is.
And what we're going to define here is a procedure called
scan, which is going to try every rule.
And we're going to start it up on the whole set of rules.
This is going to go cdr-ing down the rules, if you will,
looking for a rule to apply.
And when it finds one, it'll do the job.
Well, let's take a look at how try rules works.
It's very simple: the scan rules.
Scan rules, the way of scanning.
Well, is it so simple?
It's a big program, of course.
We take a bunch of rules, which is a sublist
of the list of rules.
We've tried some of them already, and they've not been
appropriate, so we get to some here.
We get to move to the next one.
If there are no more rules, well then, there's nothing I
can do with this expression, and it's simplified.
However, if it turns out that there are still rules to be
done, then let's match the pattern of the first rule
against the expression using the empty dictionary to start
with and use that as the dictionary.
If that happens to be a failure, try
the rest of the rules.
That's all it says here.
It says discard that rule.
Otherwise, well, I'm going to get the skeleton of the first
rule, instantiate that relative to the dictionary,
and simplify the result, and that's the expression I want.
So although that was a complicated program, every
complicated program is made out of a lot of simple pieces.
Now, the pattern of recursions here is very complicated.
And one of the most important things is not
to think about that.
If you try to think about the actual pattern by which this
does something, you're going to get very confused.
I would.
This is not a matter of you can do this with practice.
These patterns are hard.
But you don't have to think about it.
The key to this--
it's very good programming and very good design-- is to know
what not to think about.
The fact is, going back to this slide, I don't have to
think about it because I have specifications in my mind for
what simplify x does.
I don't have to know how it does it.
And it may, in fact, call scan somehow through try rules,
which it does.
And somehow, I've got another recursion going on here.
But since I know that simplify x is assumed by wishful
thinking to produce the simplified result, then I
don't have to think about it anymore.
I've used it.
I've used it in a reasonable way.
I will get a reasonable answer.
And you have to learn how to program that way--
with abandon.
Well, there's very little left of this thing.
All there is left is a few details associated with what a
dictionary is.
And those of you who've been itching to know what a
dictionary is, well, I will flip it up and not tell you
anything about it.
Dictionaries are easy.
It's represented in terms of something else called an A
list, which is a particular pattern of usage for making
tables in lists.
They're easy.
They're made out of pairs, as was asked a bit ago.
And there are special procedures for dealing with
such things called assq, and you can find them in manuals.
I'm not terribly excited about it.
The only interesting thing here in extend dictionary is I
have to extend the dictionary with a pattern, a datum, and a
dictionary.
This pattern is, in fact, at this point a pattern variable.
And what do I want to do?
I want to pull out the name of that pattern variable, the
pattern variable name, and I'm going to look up in the
dictionary and see if it already has a value.
If not, I'm going to add a new one in.
If it does have one, if it has a value, then it had better be
equal to the one that was already stored away.
And if that's the case, the dictionary is what I
expected it to be.
Otherwise, I fail.
So that's easy, too.
If you open up any program, you're going to find inside of
it lots of little pieces, all of which are easy.
So at this point, I suppose, I've just told you some
million-dollar valuable information.
And I suppose at this point we're pretty much done with
this program.
I'd like to ask about questions.
AUDIENCE: Yes, can you give me the words that describe the
specification for a simplified expression?
PROFESSOR: Sure.
A simplified expression takes an expression and produces a
simplified expression.
That's it, OK?
How it does it is very easy.
In compound expressions, all the pieces are simplified, and
then the rules are tried on the result.
And for simple expressions, you just try all the rules.
AUDIENCE: So an expression is simplified by
virtue of the rules?
PROFESSOR: That's, of course, true.
AUDIENCE: Right.
PROFESSOR: And the way this works is that simplifi
expression, as you see here, what it does is it breaks the
expression down into the smallest pieces, simplifies
building up from the bottom using the rules to be the
simplifier, to do the manipulations, and constructs
a new expression as the result.
Eventually, one of things you see is that the rules
themselves, the try rules, call a simplified expression
on the results when it changes something, the
results of a match.
I'm sorry, the results of instantiation of a skeleton
for a rule that has matched.
So the spec of a simplified expression is that any
expression you put into it comes out simplified according
to those rules.
Thank you.
Let's take a break.