1

advertisement
1
>> Wolfram Schulte: Good afternoon, everyone, and welcome to the MSR talk
series. Today, I'm very fortunate to present Philip Wadler, who comes to us
from Edinburgh. It turns out that we have, actually, met 20 years ago at some
summer school in [indiscernible], and Phil was so fortunate to then change the
world of functional programming since then, and I'm still trying to change the
world in Microsoft. Not so successful, but a little bit.
>> Philip Wadler: There are more people at Microsoft than people doing
functional programming.
>> Wolfram Schulte: And now we actually have our own functional programming
language, thanks to Don [indiscernible], so we are getting there slowly, and so
it's good that the academia is a little bit ahead of us. So I don't have to
introduce Phil a lot. I think most of you know him. He's a past holders of
the Royal Society Wilson Research Merit Fellowship, and serves as chair of ACM
SIGPLANS so everybody who has something to do with programming language knows
probably Philip. And therefore, I don't want to waste this time, but give you
the space to explore and tell us what you have done lately. Namely, give us
all some blame. Okay. Thanks a lot for joining us.
>> Philip Wadler: Okay. Thank you, Wolfram, thank you all for coming. I'd
like to thank Microsoft for inviting me. I was here because I was invited to
give a talk at BBPL, which is attached to the VLDB, and then I sort of had to
choose, do I go talk to people I know at Microsoft or hang around with VLDB. I
think I should hang around with VLDB, but I want to talk to you guys
specifically about some things, including this work, which I think might have
some applications toward what you're doing in dot net.
I thought that was an important thing to do. But also being around here is
just incredibly exciting, all the different things that people are doing, and
the huge numbers of people who I didn't realize were here who happen to be here
right now, like the [indiscernible], and I think I saw other people in the
corridor, like what are you doing here? So it's a very exciting place to be.
I'm very glad to be here. Thank you for having me.
So I'm going to get down to work. I'm going to take this off, but remind me
there's something about my jacket I'll need to tell you about later. So if I
forget, ask me.
2
Okay. So that's not so good. There we go. This is a talk that I first gave
at POPL, which was in Texas. So just imagine you're in Texas for a moment.
Now, people in the world often divide into different groups that tend to oppose
each other. I live in Scotland. There's a place near us called England.
These people don't always get along. Maybe people at Microsoft and people at
Google don't always get on. There are different fractions in the world that
don't always get on. I want to talk about one of those, okay, and how to bring
them together and maybe have more peace in the world.
I want to talk about dynamic programming languages and static programming
languages. But you all know about this sort of thing where one group feels
oppressed that there's this other group that's doing things that they don't
like, and there's some kind of conflict. Here's one example.
So I'm going to use here blue for static typing, red for dynamic typing, right,
and you can imagine those dynamic typing people are going, what are all those
namby-pamby liberal static typing people trying to bind me in for?
>>:
[inaudible] people always seem to be [indiscernible].
[Laughter]
>> Philip Wadler: I'm not going to get into statically versus dynamically
typed politics. But conflict. Let's bring some peace if we can. How can we
get these two sides talking together?
So to begin with, I'm going to talk about some work that was done previously by
myself and Robbie Findler, called Well Typed Programs Can't Be Blamed. So I'm
going to review that quickly and then go on to the new twist that was part of
this particular POPL paper. How can we make dynamically and statically typed
languages live nicely together.
So the
people
here's
remind
color coding I'm going to use is red is these dangerous redneck dynamic
and blue are these namby-pamby, liberal, statically typed people. So
a simple untyped program. I'm going to use stars as a super script to
myself when things are untyped.
So let the increment function the lambda X, X plus 1, let the apply function be
lambda X, lambda F, lambda F, F applied to X, and then apply it, take the apply
function and apply to inc and 41, and you get 42. That should be fairly
3
straightforward. Please nod if you are suitably bored by this program.
Everybody understands the lambda notation? I don't need to explain that?
Not everybody is nodding. You have to actually nod or else I'm going to pick
on you and say -- okay. Sorry?
>>:
[inaudible].
>> Philip Wadler: Well, Patrick's not nodding so maybe he doesn't know what a
lambda expression is.
>>:
[indiscernible].
>> Philip Wadler: You'll find out. Here's the same thing done in namby-pamby
liberal blue so your increment is lambda X of type INT, X plus 1, and apply.
We're now going to do this polymorphically typed in the poly morphic lambda
calculus. So these capital lambdas are binding type variables. So we'll take
as an argument the type variable X. We'll take as an argument the type
variable Y. We'll take we'll take it an [indiscernible] F, which is a function
from X to Y. We'll take as an argument X, because type is X, and we'll apply f
to x. Very wordy, but hopefully you're still bored. And then we take the app
function, apply to both X and Y. In this case they're going to be the type
INT, and we have increment in 42, and we get 42 of type INT.
So can we bring these people together? So why do I have
app twice? Ah, I see. So I've got my increment function that's untyped. And
I have my apply function, which is typed. And we need to bring these two
performed together. So I take my apply function, which has this type, and I
coerce it with this double arrow to this type, which is written star. So star,
I will sometimes pronounce dynamic. Star is the type of dynamic programs.
And now I've got an untyped code and I get an untyped result.
some typed code into my untyped program.
So I've imported
Now, you're still bored, right?
>>:
[inaudible].
>> Philip Wadler: Ah. Thank you. P is what we call a blame label, and what
we will see is that sometimes, these casts from one type to another type may
4
fail. And when they fail, we're going to use P to report where the error is.
And there's a very important aspect of P which I'll talk about shortly.
But, you know, this is a four-line program. This is not very interesting. We
don't need these techniques to deal with four-line programs. But a four-line
program I can put on a slide and show it to you and you will get bored, which
is good, right? You can follow the talk.
But what I want you to imagine is this is 10,000 lines written in Java Script,
and this is 10,000 lines written in Java, and I want to make them play nicely
together. Sorry, written in C Sharp. And I want them to play nicely together.
So that's the idea, and my claim is that this technique would scale up.
Okay. So this is what we're doing. And somebody asked about P, why do we need
P? Well, things could go wrong, right? Because this is well typed, that's not
going to go wrong. It's going to -- it's guaranteed that app meets this type
signature but star says nothing. So we could, for instance -- what have I
changed here?
>>:
[inaudible].
>> Philip Wadler: Ah, right. Thank you. It's so subtle, I missed it at
first. I've reversed the order of the arguments here. So I've written app 41
inc instead of app inc 41. And now something goes wrong, and since something's
gone wrong here blame P bar. Why does it say P bar rather than P? Well, two
things can go wrong in this cast, okay? What could go wrong?
So the cast says take something of this type and treat it as if it were this
type. And then when that happens, failure can happen in one of two ways. The
failure might be that the thing in here can't really be treated as if it were
this type. Or it might be that the environment, which is treating this sub
expression of this type, doesn't actually treat that expression as if it had
this type.
So in this case, it's the environment that's misbehaving, not the term that's
being cast. And when you blame the environment, you return bar of this blame
label. So P bar means the environment has done something it's not supposed to
do.
>>:
[indiscernible] any type always succeeds.
5
>> Philip Wadler: Well, what do you mean by succeed? The program as a whole
has failed. Okay, but you have an intuition, which I'm going to make precise
for you, and the intuition that you have is is that if you're casting from some
type to star, in fact what's going to go wrong will always be in the
environment and not with the term on the inside. And, in fact, the whole point
of this talk is to take that intuition that you have and make it precise.
>>:
[indiscernible].
>> Philip Wadler: I'll show you in a moment. Before I do that, I want to go
back and show you one important thing here. Here's our program in the typed
language. Of course, we've had to supply the arguments to the type. Normally,
we don't actually write these out. The compiler supplies them for us. But
either implicitly or explicitly, these types are there.
Now, in an untyped environment, there's no hope of figuring out what those
types should be. So even if you have some nice typed inference for C Sharp or
for F Sharp or what have you, which have very nice type inference algorithms,
you can't apply that in Java Script, because Java script has no notion what the
types are. So we'll have to be able to coerce this thing that is expecting
some type arguments into something appropriate that doesn't take any type
arguments. It only takes value arguments, because that's the only thing that
makes sense to do in the dynamically typed world. You don't have types to pass
in.
So part of what we need to satisfy is to figure out how to coerce something
with a ferrule type into something that doesn't have a ferrule type. So that's
one of the things we're going to need to do to make all this work. So that we
can very naturally call typed things that are polymorphically typed in an
untyped environment.
So this is Tom's intuition. A cast from a more precise to a less precise type
may blame the context containing the cast, but not the term contained in the
cast. So, in fact, we're guaranteed app has this type. Nothing goes wrong
with app, Tom said, but the environment, being untyped might pass to app things
it's not expecting, which makes things -- which make things go wrong.
And what we're going to do is set things up so that the typed world can rely on
the usual typed guarantees. Your C Sharp compiler or your F Sharp compiler is
6
expecting things to be well typed, but your Java Script is not necessarily
supplying well typed things. So we need to make sure that the thing has the
right type before we pass it in. Part of the system is to satisfy those
guarantees.
Let's look at it the other way around.
>>:
Does it cast itself [inaudible] in the environment?
>>:
The cast itself?
>>:
Is that considered to be in the environment?
The cast itself?
[Indiscernible].
>>: When something goes wrong, we're always going to blame either the
environment or the term contained in the cast. We're never going to blame the
cast itself. Robbie Findler presented a very nice paper with co-authors at
POPL that talked about systems involving dependent types where you might
actually want to blame the cast itself. But we're not going to do that.
So here's the other way around. Before we had a mostly untyped program in
which we inserted a typed component. Here's a mostly typed program in which we
insert an untyped component. So now, app has been written in Java Script. Inc
has been written in C Sharp. Again, we take app star and cast it from type to
star to the type for all X, for all Y, given a function from X to Y and then in
X return to Y. And then we apply ap[ to int to incident, and incident inc 41.
Now it's going to check at run time -- sorry, at compile time that all these
constraints are properly satisfied. And, of course, we get 42.
Now what can go wrong? Now, what we've changed is -- what's changed this time?
Oh, yes, instead of writing lambda F lambda X, F of X, I've written lambda X,
lambda F, lambda XX. So if you think about it, the type of this thing is for
all X, for all Y. X goes to Y, goes to X goes to X, because it's returning an
X, not a Y. And we want to be able to check that at run time. We need to
ensure that this function satisfies this constraint so it returns an X,
something needs to go wrong.
Oh, that's pretty interesting. How are you going to do that? So solving that
is another one of the hard problems we're going to need to solve here.
So in this case, it is wrong, and what we're going to do is blame P, which says
7
this time it's not the environment. The environment's perfectly fine. It's
the term inside the cast that's gone wrong. Okay. So that's what blame is
about. Blame is about saying, is it the term outside the cast or inside the
cast that's gone wrong? The blame label itself is probably the most important
thing. It says something here at this cast has gone wrong. Blame is the next
thing it tells you, do you look on the outside or do you look on the inside.
But in particular, one reason that blame is nice here is it lets us formulate
what we mean by type soundness in this context, because this notion of having a
statically typed world in a dynamically typed world and interfacing them has
been around for a while, and in particular Robbie Findler and Matias
[indiscernible] wrote the first papers in this and they came one this notion of
blame. And there was an intuitive result, which one wanted, which is when you
have a more precise and a less precisely typed size to this cast, and something
goes wrong, it should always go wrong on the less precisely typed side.
Normally, type safety says things don't go wrong. But we can't say that here.
We've got dynamically typed stuff. Of course things might go wrong. So we
need a different kind of type safety theorem. What type safety theorem do we
want when it says when something goes wrong, it's on the less precisely typed
side.
So the way I would think about this is, right, I'm a Haskell sort of guy.
Robbie at the time we started this was a scheme sort of guy. The scheme people
have renamed themselves so now he's a racket sort of guy. But what I wanted to
say is right. We were trying to interface my code to Robbie's code.
Something's gone wrong. I want to know for sure that it's guaranteed. I can
blame Robbie. Okay? So that's what the blame theorem is about, blaming
Robbie.
I think I saw a hand go up.
Was there a question?
Yes.
>>: So the way you've defined which side to blame actually assumes that you
can actually typed the untyped [indiscernible] because you're saying blame
always goes to the less precise. But what's interesting about down time
calculus is that you don't have types.
>> Philip Wadler: Actually, I'm not sure if they're in this talk; but no, I'm
going to show you the type rules for this language, and the untyped calculus
does have a type. So it's an early paper by Robbie, and it's got all the
8
syntax rules for scheme. And after them, it says colon, TST. So you have
these type rules, the usual type rules one would see, except all the types in
every single type rule with TST. What's going on here? I start reading the
paper. And eventually, I read it, and it says, ah, TST stands for the scheme
type.
Scheme is a perfectly well typed language, but everything has the same type.
>>:
Sure.
>> Philip Wadler: Bob Harper has a nice aphorism for summarizing this.
Untyped means uni-typed. So that's what star is. It's the type of everything
in the untyped world. I can't recall if it's in these notes. No, it's not.
But, in fact, there's a very -- so I've written all my untyped things with
these brackets around them. There is a very easy way to coerce these untyped
things into this language. In fact, let me just show you what it is.
So this is untyped lambda calculus. And the typing rules is X has type star.
If N has type star, lambda XN has type star. If L has type star and M has type
star, L applied to M has type star. Right? Very simple typing rules.
But we can convert them, these into terms in our language with cast of type
star. So here's the conversion, X converts to X. Lambda XN converts to lambda
X of type star N. Wait a minute, the type of that isn't star, right? The
argument is star. N is of type star. So this is of type star to star, which
we cast to type star. So we need a cast there.
Similarly, L applied to M, L is of type star. Let's cast that to be of type
star to star. We can then apply it to M, which is of type star. If you take
something of type star to star, apply to something of type star, the result is
of type star.
So we've taken our untyped language, which really there's language with
everything of type star, and converted it into this language. So we can embed
the untyped lambda calculus easily in this language.
Okay. So that's what we've done before. How do we extend things to deal with
polymorphism? So first, let me go -- what am I doing here? Ah, right. Do I
explain this? No. Yes. Let's look at this for a moment. I haven't explained
to you just how incredibly tricky doing what we want to do is going to be. Let
9
me explain why it's tricky.
Now, remember, something needs to go wrong here if we just return X instead of
F applied to X. So how is it going to know something's gone wrong? Normally
what you do with polymorphic lambda calculus, what do you do if a lambda
expression applied to an argument? You substitute the argument for the bound
variable.
So if we have big lambda X, big lambda Y, apply to INT and INT, that's easy.
Just substitute INT for the big X and INT for the big Y. What happens here,
once we've applied this to INT and INT, it says we need something whose type is
INT goes to INT, and INT gives an INT, okay?
And what we're doing, we then give it the inc function, which we ignore, and
41, which is of type INT, and we return X, which is 41, which is of type INT,
and everything's hunky-dory. No, it's supposed to give us a type error. Whoa.
So whatever we're doing here, it can't be simple substitution for types.
Something trickier is going on.
Now, you might say, well, hey, what's so wrong if it doesn't give a type error
here? Why don't we just say, it's fine if you pass this in and they're both
INTs, it's okay, just return 41. We don't mind. Why not do that?
I'm going to put on my jacket. Now, you'll notice
lovely lambda pin. This was given to me by one of
made it out of silver. So it's one of my proudest
calculus is cool. Let me tell you one of the cool
calculus. It's called semantic parametricity.
on my jacket, I have this
my students who actually
possessions. Lambda
things about lambda
The polymorphic lambda calculus, you may know, was discovered by two different
individuals. John Reynolds and Jean-Yves Giraud almost at the same time,
within about a year of each other, Jean-Yves Giraud, a magician, discovered it
slightly earlier. John Reynolds independently discovered it later. They each
proved really cool theorems about the polymorphic lambda calculus. Jean-Yves
Giraud proved a representation theorem, which I won't go into in detail right
now. John Reynolds proved a property that we now call semantic parametricity.
What this says, roughly speaking, is a term in polymorphic lambda calculus
always takes related arguments into related results.
And in particular, this applies to the polymorphic type variables.
It says
10
that there's an interpretation of a type as a relation where each type variable
becomes a relation variable that you may instantiate any way you wish.
And this captures exactly the notion of data abstraction. Says whenever you
have a type variable, you've got two different arguments, even of different
types, as long as they're related, the results might be related.
So let me tell you what that means for -- oh, I've got a laser pointer that I
never use. I'm going to use it. It's very powerful. I once gave a talk in
front of physicists. And as a reward, they gave me the laser pointer.
Physicists know much more about laser pointers than computer scientists. So be
nice or else I will lase you.
Right, so what this says is take two different applications of the app
function. I'll do it. Here. That one's dead. Let this argument and this
argument be related as follows. That whenever the arguments to these two
functions are related by R, the results are related by S. Okay? So we have,
for every X related by R to X prime, that that implies that F of X is related
by S to F prime of X prime. So this relates F to F prime.
And say, here I've got an argument Y and an argument Y prime that are related
by R. From the type alone, I can conclude that app FY is related to app F
prime Y prime by S. Every function, every function, not just app, any function
at all of this type satisfies this theorem. Just write out the theorem in
full.
For all F and F prime, satisfying that so I'll just write this in full too.
For all X and X prime, such that that holds, and then for all Y and Y prime,
such that Y relates by S to Y prime. So if is true, and this is true, then we
have app FY relates -- oops, these relate by R. By S. I suppose I should give
the arguments for app, right? Of course, F and Y and F prime and Y prime have
to have the appropriate types.
So this is always true just from the type alone. And nothing else. This is
really powerful. Okay? It's a very useful thing. It says, for instance, from
the type of a sort function, you can know certain things about the output.
From this particular type, it's very strong. It tells us the only thing the
app function can be is the thing that takes a function here and an argument
here and applies the function to the argument to give this result. There's
actually one other possibility. It might not terminate. And that's it. Those
11
are the only two possibilities. It's either the constant non-terminating
function or the identity. That's it. Very powerful result.
We want to maintain -- so that's called semantic parametricity. I can now take
off my jacket because I've shown you a nice bit about lambda calculus. That's
cooler. But that result was cool, right? That's John Reynolds' result. Very
nice. Theorems for free. Just from the type of a polymorphic function, you
can know a very powerful theorem that it satisfies.
So we would like to maintain semantic parametricity. That's why we want to
make sure that this gives us a type error, because we want to be sure that if
we take an untyped thing and cast it to a polymorphic type, the result of the
cast behaves polymorphically and satisfies semantic parametricity. That's what
we're trying to achieve here.
So to do that, we're going to need to modify polymorphic calculus a bit,
because I just told you what you normally do is you just substitute INT for
each of the type variables. We can't do that. We have to know the X and Y are
distinct types in order to get the appropriate checking for semantic
parametricity. What we're going to do instead is introduce this new
constructer, pronounced new. So we say new X gets A and T has type B. And the
typing rule for this is that we're going to type check B on the assumption that
X is replaced by A.
So in the type checking, we do the substitution, but we're going to keep X
around as a separate type distinct from A. And now, the normal reduction rule
for polymorphically typed lambda calculus is big lambda X, some term applied to
a type. What you would do is you would just replace X by A within this term.
But we're not going to do that. Instead, we're going to introduce this new
construct at the front.
And then the typing rules are pretty much what you would expect. There's
something a little bit surprising here, which is I no longer allow every term
inside a big lambda expression. I only allow values. But that's not really so
surprising, because in languages with side effects like ML, you need to make
that restriction in order to get type soundness.
So we should expect that restriction. Because we do have side effects here we
might raise some blame. So that's why we restrict to values.
12
And I reference here a paper by -- a very nice paper by Neis, Dreyer and
Rossberg, where they do something quite similar. The interesting thing is in
their paper, they have a global list of bindings of type variables to types,
where we're doing it locally with this construct. We tried to do it globally.
It didn't work out neatly. So deciding to do it locally was a big advantage.
And then having done that, we can now just introduce the one construct of blame
calculus, which is very straightforward. It says that if S is of type A, and A
is compatible with B, I think I show you the definition of compatibility later.
Don't worry about that for now.
Then we can cast S from type A to type B, and the result, of course, has type
B. So that's a fairly straightforward rule.
So we often -- I will usually say cast, but you can also say contract. And
what we're promising here, the contain term is we'll provide an A. The type
checker guarantees that. But we're saying, you'd better also -- you better
provide an A that can be treated as if it were a B. Similarly, the containing
context must treat this term as if it were a B. The type system guarantees
that. But the contract is saying, you better treat that B as if it were an A.
So we're -- the inside thing is guaranteed to be an A, but the outside thing is
only guaranteed to treat it as a B. It had better treat it as an A. If it
doesn't, we're going to blame the outside. Similarly, this A -- this is
guaranteed to be an A, but it better be treatable as a B. And if it's not,
we're going to blame the inside.
So here's the definition of compatibility. And it's not too surprising, star
is compatible with everything else. That's what we'd hope. If A prime is
compatible with A and B is compatible with B prime, then A to B is compatible
with A prime to B prime. That makes it -- notice the swapping around. That's
called contra-variant. You get it whenever functions appear. People have seen
contra-variant function rules before. Most of you will have done, I expect?
Yes?
And then the other rule says if A it's compatible with B and X despite appear
free in A, then A's compatible for all XB. And if A with X replaced by star is
compatible with B, then for all XA is compatible with B.
And this was sort of a surprise, because if you don't have polymorphism,
13
compatibility is a symmetric relation. But we were a bit surprised to discover
that when you do have polymorphism, it becomes asymmetric, because this and
this are different. And you'll see why they're different in a moment. Here
are our reduction rules. So let's say V is a function from A to B, and we cast
it to a function from A prime to B prime. How do we do that? Well, we need to
return a function from A prime to B prime. So we accept an argument, X, whose
type must be A prime. V is expecting an A. So we take X, which is an A prime,
and cast it to A. Now V has an A. We apply V to A, and it returns a B, and we
cast that B to a B prime.
Notice that we started with A to B goes to A prime to B prime. The blue bits,
right, B goes to B prime here. But for X, we're going the other way around.
We're not going blue to red. We're going red to blue. So blue to red cast has
changed into a red to blue cast. That's why we get contra-variants. And that
swapping, where red to blue swaps around to blue to red, is where we change P
to P bar. So see, I've written P bar there, rather than P.
So that swapping around that happens when you apply a function is sometimes how
you end up blaming the environment rather than the term contained in the cast.
Yes?
>>: I'm a bit confused. It's a very basic question, but in your examples, you
said that the dynamic language, everything is type star.
>> Philip Wadler:
>>:
Correct.
You go from dynamic to type, or vice versa.
>> Philip Wadler:
Yes.
>>: So I don't why we need to know anything more than the first two things?
Where did you get more [indiscernible] things in red from? I don't really get
that.
>> Philip Wadler:
>>:
You will see.
I mean, you show this stuff on the board --
>> Philip Wadler:
Oh, where does it come in?
14
>>:
Yes.
>> Philip Wadler: Well, here. Here's a cast with type star to star. So if
you have something in the dynamic language, as you execute it, it's actually
going to do this cast.
>>: So you're saying you're going to sometimes write that sort of cast with
star to star in your program?
>> Philip Wadler: You don't need to write it. If you write an untyped
program, when you embed it into this language, the embedding, the compiler,
will introduce this cast.
>>:
Okay.
>> Philip Wadler: But there are also other ways in which it arises. Do I have
that rule here? No. This is not all the typing rules. There are other rules
that introduce that as well. For instance -- that's okay.
>>: When I asked you about the
[indiscernible], in order to be
I should be able to give a type
because star is compatible with
>> Philip Wadler:
>>:
have
need
star
Yeah.
So I actually need something much more specific in that example where you
-- you've forgotten the application for all X [indiscernible] X are Y. I
to give a type to that term that's much more specific than star, because
would be compatible for all X and all Y.
>> Philip Wadler:
>>:
Correct.
untyped -- going back to an example
able to detect that that type cast is not okay,
that's more specific than a star to determine,
any type.
Oh, you mean, why is this going to go wrong?
Exactly.
>> Philip Wadler: I'll show you that. I'll show you how that goes wrong.
This shows you what happens, in fact. If we cast any type to the type for all
XB, what we'll do is move that big lambda X to the front. So now we've got
something that's typed as for all XB, because the type of this is B. And we -V had type A, so we now cast V from type A to B instead. But notice we've got
15
this lambda X here. That X is going to stick around, because eventually, that
lambda X dock term is going to be applied. When we apply it, we're going to
get new X gets A. But X still appears within V.
>>:
So A and B are here type schemes or just base types?
>> Philip Wadler:
>>:
It could be polymorphic?
>> Philip Wadler:
>>:
A is any type you want, including a polymorphic type.
Including a type with free variables.
So why can't I just [indiscernible] then?
>> Philip Wadler: Let me go on. We might -- I don't have the example in
detail. Maybe we'll just go through the example in detail to see what happens.
If you've got something of polymorphic type that you're asking to type B, so V
has type for all XA. It's expecting a type argument. What type argument
should we give it? INT? Bull? INT to INT? You can see it here. Let's give
it star.
Well, wait a minute. Does that work? I'll say more about whether that works
in a minute. So V applied to star, of course, has type A with X replaced by
star and then we cast that to B. Now, you can see why these rules all look the
way they do. Right? If this cast is permitted, then that means -- if this
type's compatible with this type, the only way that can happen is if this type
is compatible with this type, and this type is compatible with this type.
So guarantees that as you reduce, you maintain compatibility. And that
similarly, these rules guarantee that this -- if this is compatible, this is
compatible. And if this is compatible, this is compatible.
So that's why you get the type substitution appearing here. It comes in order
that this rule maintains compatibility. And that's why symmetry gets broken.
Then finally, we have rules -- there are some other rules, but here's the
important ones. Eventually, you'll end up casting from type X to type star and
then back to something again. Okay? If we cast from X to star to X, that's
fine. But if we cast from X to star to Y, where Y is different from X, then we
16
blame somebody.
>>:
Only if X and Y are independent.
>> Philip Wadler: Only if X and Y -- any two type variables, X and Y, that are
not identical, are incompatible. In particular, even if X and Y are both bound
to INT, they're still incompatible. That's how we get that to work.
Would people like me to take the time to go through applying these rules to the
particular example involving -- to that example to see why we get blame P? Do
things in that detail? Okay. Here we go.
>>:
[Inaudible].
>> Philip Wadler: And they we're going to apply this to INT, INT, inc and 42.
Okay? No, 41. Have to get that right. So then this will become, after we
apply these various rules ->>:
Can you show us the reduction rules on the slides?
>> Philip Wadler: Sure. Good idea. Thank you. And this has label P. This
becomes lambda X, lambda Y. Okay. So now we've got lambda X applied to I, so
that that becomes new X is I. And then we've got lambda Y, I, and that becomes
new Y is I.
This bit just stays the same. So I'm not going to copy it every time. So
we've got the two news. Now this, you remember, compiled out to lambda F of
type star. Lambda X of type star, X. So the type of this, after we do some of
the casts, I'm not going to go through those in detail, is star goes to star
goes to star. Do you want to see the detail of that? I was going to skip over
that. Okay? Goes to X to Y to X to Y and then the whole thing apply to inc
and 41.
So it's in this step that you actually get some types that are more complicated
than star. Okay. So then this becomes what? Lambda F prime, where the type
of F prime is X goes to Y. I'll just simplify this all at once, right? And
lambda X prime, the type of X prime is X. And then we have X, which is going
to be cast from -- sorry, then we've got all the arguments in there.
I'm going to do several steps here at once, because it will make life easy.
17
Inc is of type X to Y, which gets cast to star. 41 is of type X, which gets
cast to star. We substitute those in and then cast the result from star back
to this. So that simplifies to 41 is F type -- sorry. 41 is of type X, which
gets cast and there will be a swap in here. This is P bar. It's cast to star.
And then gets cast to the result type, which is Y. So after several steps,
this reduces to this. 41 has type X, because that's the type it's given. We
cast it into this world so it has type star. Then we take this X, which has
type star, and cast it back to type Y. Turns out this cast is labeled with P
bar. This is labeled with P. Okay? X is different from Y so this is where
things fail. Things have gotten labeled with Xs and Ys. If we substituted,
this would be INT and everything would be hunky-dory. This is important why we
don't substitute.
>>: I can see by your rules how that fails. And I can see how the other one
that was correct worked. Because you still have an INT and an INT.
>> Philip Wadler: The other one, it was F applied to X. And since F has type
X to Y, F applied to X has type Y. And then we cast Y to Y, and that works
okay. Good question. Thank you. And you can see by this rule that we're
going to end up blaming Q, as it happens. So we end up here blaming P, the -I think I might have gotten P and the P bar and a Q mixed up. I'll skip over
that. So that's basically how it works.
>>: So when are you allowed to use the environment assumptions that X equal A?
My worry would be that somehow are able to read that information ->> Philip Wadler:
>>:
Oh, where does this get used?
Yeah, exactly.
>> Philip Wadler: It gets used, for instance, in checking that 41 has type X.
How did we know 41 has type X? Because X, in fact, is it integer.
>>: I see. So whenever you have a compatibility check between a non-type
variable and a type variable, that's when you ->> Philip Wadler: There are specific rules about that which are in the paper,
and which I'll show you a little bit about. But basically, I'm going to refer
you to the paper for all the details of how that works, if that's all right.
18
Now, you remember I said that we decided to instantiate V with star. I said,
well, how do we know that's any good? We know that's good because of the bit
with the cute name. The jack-of-all-trades principle. So if V has type for
all XA, and A with X replaced by C is compatible with B, well, if A with X
replaced by C is compatible with B, then A with X replaced by star will also be
compatible with B, because of the compatibility rules, since star is compatible
with everything.
Then consider V applied to the type C, that has type A with X replaced by C.
We can cast that to B. And compare that with V applied to star, which has this
type, which we cast to B. We'd like these to be related. In fact, what would
be nice if they were the same always. But that's not true.
Turns out, if you put a C in here, that's additional information and it might
fail more often. So this can fail when this doesn't fail. Okay? But if -yes. But if this gives an answer, it's the same answer this would give. So we
never get the wrong answer. We always get the same answer you would get for
any C.
Okay. We're at 4:30. So I'm not going to say very much about the dauntingly
complicated but rewarding bit. But we actually have not one, not two, not
three, not four, but five different relationships here.
One of these you've seen before, which is compatibility. What are all these
other things? We have four different flavors of sub-typing. Traditional
sub-typing uses this contra-variant rule. If A prime is a sub-type of A and B
is the sub-type of B prime and A is a sub-type of B, then A prime is a sub-type
of B prime. Usual contra-variants rule.
This is flipped around. Notice that the rules for sub-typing for polymorphic
types, these are all traditional rules going back to papers by John Mitchell at
least. Talking about sub-typing and polymorphic lambda calculus. And notice
they're very similar to the compatibility rules, which is good, right? The
only difference is C has been replaced by star in the compatibility rules.
And you can prove that if you cast from A to B, and A is the sub-type of B,
then that cast never fails. Okay? So that's a nice thing.
We also define positive and negative sub-typing, okay? And the difference is
in positive sub-typing, A is always a positive sub-type of star. In negative
19
sub-typing, star is always a negative sub-type of B. All the other rules are
just the same. Here we've written positive everywhere except here, where the
contra-variants happens. We flip positive to negative. Here, where the
contra-variants happens, we flip negative to positive. These are very much
like the sub-typing rules with positive and negative labels. The key
differences are here.
And then you get a rule that says if you've got a cast from A to B, if A is a
positive sub-type of B, then you never get positive blame. If A is a negative
sub-type of B, then you never get negative blame. So this is how we prove
where blame can and cannot arise, using positive and negative sub-typing. It's
a fairly easy and straightforward proof, in fact. It's quite nice.
And then here's the really cool bit. I don't know about you, but the very
first time I saw the contra-variant sub-typing rule, right, I said, oh, you
know all the contra-variant sub-typing rule. And you all went yeah, yeah,
yeah, we're used to this. The first time I saw it, I went, well my head hurts!
Why is that swapping around? Right? What rule were you expecting to see? Not
the contra-variant rule. But the naive sub-typing rule. If A is a sub-type of
A prime and B is a sub-type of B prime, then A to B is a sub-type of A prime to
B prime, right? That's what we'd expect.
Right? Turns out the math says it doesn't work. But it's very natural.
That's what you want to see. It's too bad it doesn't work. Right? Bertrand
Meyer, when he designed Eiffel, the sub-typing rule was this sub-typing rule.
I got to meet him many years later at a history of programming languages
conference and I said, you know, didn't you realize that something went wrong
there? And he said, well, yeah, but I thought it was a bug in the compiler.
This seems a very natural rule to use, and so far, in the history of
programming languages, every time this rule has appeared, it's only been to
give you a type non-soundness result. Right? It's been a bug. Now, at last,
we can use this rule! The reason we can use the rule is we have the following
theorem. The A is a naive sub-type of B if and only if A is a positive
sub-type of B and B is a negative sub-type of A. Isn't that cool? It's a
naive sub-typing arises out of these weird positive and negative sub-typing.
So positive and negative sub-typing probably made your head hurt, but you put
them together the right way, you get naive sub-typing. Isn't this lovely?
Right, so an immediate consequence of this is if A is a naive sub-type of B,
20
then you never get blame B. And if B is a naive sub-type of A, you never get
blame B par. So what that means is naive sub-typing exactly corresponds to the
notion you want of less precise type. Right? The naive -- the bigger naive
type has more stars in it.
If you're casting to something with more stars and something goes wrong, it's
always the fault of the side with more stars. Just what you'd hope. The
surprise ending is not that. I'm not going to go through this in any detail,
but turned out that in doing the proof, the surprise for us was we needed to
change around the notion of exactly how you manipulate things in polymorphic
lambda calculus. And we introduced these things we call static cast, which
looks just like the casts I've shown you, but they're corresponding to dealing
with certain type variables, right. They're dealing with this substitution,
replacing X by A within the type.
So we have a cast that's just part of the manipulation to get the type checking
right that says cast B by replacing X by A and then because when you do
applications things flop around, you need to cast the other way as well. And
we have various reduction rules for these things. And the only thing -- so the
surprise was that there is a variant of polymorphic lambda calculus that we
needed, and it used casts remarkably like the casts that we already wanted for
casting between dynamic types and other types.
This is a different casting system, but it looks very similar. And the nice
result you get is you then have a canonical form result for the polymorphic
lambda calculus. So normally, you get polymorphic result that says something
is either a base type or a function type or a polymorphic type, and that's it.
But now -- and the type variables have vanished because you substitute them
out.
But in our case, we're not substituting them out and so you get one more type,
which is the polymorphic type, a type variable, and that always has the form of
one of these static casts to a polymorphic type variable. You retain enough
information that you can actually tell at run time when something has
polymorphic type, which is what we need in order to get semantic parametricity.
It turns out that a type system very similar to this had been done very ten
years ago by Dan Grossman, Greg Morrisett and Steve Zdancewic.
And we were just amazed to discover this, and then we looked and we saw that
21
both Dan Grossman and Steve Zdancewic were on the program committee for POPL,
and we thought, we're in! So that means if you actually look at the paper,
you'll see many different calculi in there, including the -- just an ordinary
polymorphic calculus. You don't need to worry about blame. The static casts
still give you interesting information about types. And we have similar
versions of everything with blame.
To conclude, let me say something about notation. Because it took us years to
find the right notation. We started by writing cast A to B with blame in P.
And then we wrote arrows backwards from A, cast A to B, so the whole thing has
type B. And finally, we hit on doing this. Saying S has type A and you cast
it to type B. Notice that in a real program, when you're really doing a cast
you, can omit this part. This is only there to make the theory work out well.
You can infer the type of S, which can be A. In a practical language, you
wouldn't need this bit.
But the theory became a lot easier when we do this, because, right, you want
compositions to be easy to read. Well, this way around, it's a real mess,
because things don't line up. That's why we made things to go backwards. It
lines up A to B, and B to C, which is great as long as you naturally read
things from left to right. And I'm Jewish, but still I didn't like dealing
with that.
So finally, we turned the notation around, we said cast A to B and B to C, and
now it looks nice. And even there's an obvious abbreviation, which you saw in
the Us, which instead of saying cast A to B and then cast B to C, you can just
say cast A to B to C. So notation is good. And getting it right can take many
years.
So that where we are now. You all know things in dot net like C Sharp which
has this dynamic type. One reason I'm here is I want to talk to some of you
about this, because I think that these ideas could fit very well with what's
already in C Sharp and the dot net platform for dealing with dynamic types and
now you can have these casts inserted so that very automatically, you can say
right, that thing of type dynamic, look, I want you to check for me it really
has the type for all X, for all Y, X to Y to X to Y and give me an error if it
doesn't. You can guarantee very precise properties of your programs at run
time, which would be very handy.
Okay.
Thank you very much for your attention.
I've gone a bit over time, for
22
which I apologize, but thanks very much.
>> Wolfram Schulte: Thanks a lot. He is here until tomorrow afternoon. He
took already a lot of question during the talk so we will break up now. A
little time now, if you have more questions, maybe you come to the podium.
Otherwise, contact me to see whether you can meet Phil tomorrow afternoon.
>> Philip Wadler:
very much.
The more people I can talk to, the better.
Thanks again
Download