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