Sie sind auf Seite 1von 5

[music].

This segment has perhaps the most counterintuitive result in the entire course, but it's also exactly the sort of thing that you take a programming language course to learn. So we're going to discuss subtyping for functions and let me be clear about what I mean here. Now, there's some things we already know. We know if you're the caller to a function. You can pass a subtype of the argument that the function is already expected. And similarly, after you get back something from the function, like maybe you get back a color point, you can think of that color point as a point. This is all normal subtyping, okay? It doesn't matter that were using of the calling the function. More interesting is to ask the question when is one function itself a subtype, having a type that is subtype of another function type. And this is important in a language with high-order functions. Suppose the function argument is itself a function, something of type t1 arrow t2 using our ML syntax for function types takes a t1 and returns a t2. If that's the argument to some other function, are you allowed to pass in a t3 arrow t4 instead and under what circumstances? Clearly, t1 arrow t2 and t3 arrow t4 are going to have to be related somehow. You can't just pass in some totally different function with some totally different type. But is there a subtyping relationship there? And let's try to figure out what it is. This is not just important for subtyping and languages with higher-order functions. What we're going to study here is also directly relevant to subtyping for object-oriented languages and we'll study that next. It's just a little easier to understand in my opinion thinking about functions first. So here's an example we're going to build off of, there's actually no subtyping on this slide, but I just want to show you a higher-order function involving records. So, it's a function distMoved. It takes in a function f and a point p. It calls f with p, and then, it returns the distance between p and f of p,

alright? That's all it does. It's a little function. So f has type x colon real, y colon real arrow x colon real, y colon real. It takes a point and returns a point, p is a point and then the body here calls f with p, determines how much the x coordinate change, determine how much the y coordinate change, and returns the appropriate distance. Here is a completely normal call to it. Here's a function flip that takes a point and negates the x-coordinate and the y-coordinate to produce a new point. So it's a functional, flip it around on both axises axis. And then we called this moved with the function flip in some point like x-coordinate is three, y-coordinate is four. There's no subtyping here yet because flip has type x colon real y colon real arrow x colon real y colon real, which is exactly the type the distMoved expects, okay? So this works fine. Now, let's consider this call. It's exactly the same definition of distMoved. But now, I want to pass in this function flip green. What it does is it make, it builds a new record that flips x, flips y and adds a color field green, okay? So the return type of flipGreen is x colon real, y colon real, color colon string. So flipGreen overall takes an x colon real, y colon real and returns an x colon real, y colon real color colon string, distMoved does not expect exactly that type. It expects an x colon real y colon real arrow x colon real y colon real. But it's no problem if we add this subtyping rule, that if ta is a subtype of tb, then t arrow ta is a subtype of t arrow tb. In other words, we're allowed to go into the return type of a function and add subtyping there. So, a function is allowed to return something with fields we don't need, something extra. A function can return more than it needs to. There's jargon for this, we say that function types are covariance in return type. I will not test you on that jargon, but if you ever heard the term covariance that

mean subtyping goes the same way and here the subtyping goes the same way. If ta is a subtype of tb then t arrow ta is a subtype of t arrow tb. So, that works great for the return type, but what about the argument type? So here is a different function we're trying to pass in to distMoved, flipIfGreen, flipGreen says if t's color is green, then build this record with an x field and a y field. Otherwise, build this other record with an x field and a y field. Now, the argument type of flipIfGreen is x colon y colon real color colon strings. It require all three fields. But up here, in this move, we pass to the function something that does not have a color field. If you look at this call, we called this move with flipIfGreen and something that just has an x field and y field, this will get stuck. This, if you run, try to run this program, it's not sound. It will try to read the color field of a record that does not have a color field. And therefore, we better make sure this code does not type check. And so, what we cannot allow is subtyping on argument types. Just because ta is a subtype of tb you cannot allow ta arrow t to be a subtype of tb arrow t. You cannot say, oh, it's okay to assume this function doesn't need all the fields. It does need all the fields or at least it might and so you cannot drop fields from arguments to functions. So, this better not type check, we better not have this type checking rule. But what may surprise you, is it's okay to go the other way. It's okay to allow subtyping on function argument types as long as that subtyping is backwards as long as it goes the other way. You see the rule here in this last bullet at the bottom. It turns out that if tb is a subtype of ta, then ta arrow tt is a subtype of tb arrow t. See how I flipped it around? The subtype here is in the supertype of the result. Okay? So this has a jargon. It's called contravariance. But what it's saying is a function can assume less than it needs to about

arguments. So, here's your example. It turns out, it's fine to call distMoved with a function that does not need an x colon real and a y colon real, maybe it only needs an x colon real. Here is an example, this function flipX_Y0 takes in a point, returns takes in some p, returns a new record that has an x field that's minus p.x and the y field that's just zero. So notice it never asked for p.y. It does not care if p has a y field. So if I pass flipX_Y0 in the distMoved, distMoved needs f to work as though it had this type. And flipX_Y0 can act like it has that type, even though, it does not need its argument to have a y field. So, flip xy0 has an argument type of x colon real, but we can pretend it has an argument type of x colon real, y colon real. Yet, x colon real, y colon real is the subtype compared to x colon real. And that's why this subtyping rule is reversed, this is the correct rule. It says that if tb is less than ta then ta arrow t is less than tb arrow t and it turns out you could do both at once. So here's a final example flipXMakeGreen returns a record with an x field and a y field and a color field and only requires its argument to have an x field. So we can pass this end for distMoved, flipXMakeGreen has type, if you give me an x colon real, I'll give you back an x colon real, y colon real color colon string. If you just look at flipXMakegreen, that's the type it should have. We can pass it into distMoved, which expects an x colon real, y colon real arrow x colon real, y colon real and nothing bad will happen, because this function works just fine, if you give it something with an x field and a y field. And it always gives back something that has an x field and a y field, it just happens to give back more and it happens to need less. We're just combining the two ideas together. And that's why the general rule for function subtyping is that if t3 is as subtype of t1 and t2 is a subtype of t4, then t1 arrow t2 is a subtype of t3 arrow t4. So, we see for the result types, t2 and t4, that subtyping goes the same way and

for the argument types t3 and t1, it's flipped around and it goes the other way. So, the fancy jargon way of saying this is that function subtyping is contravariant in it's arguments and covariant in its results. And this is essential for understanding subtyping in object-oriented programming, actually, and understanding how it is sound to override methods in a subclass in a language with subtyping and we'll see that next. I would just like to be honest as I said at the beginning, that I think this is the most unintuitive concept in the course. When you get confused I can encourage you to go back and work through the examples or make up your own examples and convince yourself that the arguments really are contravariant. They really do have to be flipped around, otherwise, you can break the soundness of the type system. Now, many smart people have gotten this wrong, have made mistakes about this, and I just want to emphasize this point, that at some point in your life, you may make this mistake, your boss might make this mistake, your friend might make this mistake. And so I'm trying to get you to memorize that there's something weird going on here and you can always come back and watch this video later. And if nothing else, I want you to know that I do have a PhD in programming languages. And I am happy to jump up and down and insist that subtyping for functions and methods has to be contravariant in, In the arguments. I'm going to, to do this quite literally, because I think it's silly and I think it's fun and you might remember it that way. So here's what I'm going to do. I'm actually, I moved the chair to the side, I'm actually jumping up and down. And while I'm jumping up and down, I'm telling you that function subtyping has to be contravariant in the arguments and it's not the direction that you naively think it is. And with that, we can conclude our study of function subtyping.

Das könnte Ihnen auch gefallen