 Whoa, that's really loud. That's fine. All right, happy Friday. Woo! No, not a woo. You're not even happy that it's Friday. Next. No. All right. Cool. All right. So we have, I really want to get through Hindley-Milner type inference today. I think we're gonna do it. I have faith in you, in me, in us. So let's keep going where we were looking at implicit polymorphism, and if this thing would work. Yes. Cool. Okay. So when we left on Wednesday, we were talking about functions, and we were talking about how can compiler techniques automatically, how can a type system, you all ready? Cool. How can it automatically infer the types of all the variables in the program? Right. So this is different from dynamic typing. It's not checking the type system at runtime. This is static time before executing the code. Can we automatically infer based on the usage all the types in the program? So we saw here, we have a function max, which takes in two values, an x and a y. And then we say, if x is less than y, then return y, otherwise return x. So you can think you can use this function to pass two into objects, and it will decide which one of them is greater than, and it will return that one. Greater than, yeah. So we talked about what's the type of max. So what is the high level type of max? Function. It's a function. Cool. So then what's the more specific type function that takes in two parameters? What were the types of those parameters? T. So T and T returns the type of what? T. Right. That is the most general type that is applicable to this program. Right. So one way to write that, which is what we did in the type system section, right, is we said a function of a and a returns a. Does it matter that we used a here instead of T? No, because we're talking about any type. What's important here? The same so that both of the parameters are the same and the return type is the same. Right. That is the only thing that's valid for this program. Doesn't matter what you put in for a, just like in an algebraic equation, it doesn't matter if you substitute X for another variable that's not used. It's still the same equation. Right. So here we're using a to represent any type. We could use T or you can use T one, whatever we want. As we'll see, as you've seen in your homework, right. And from here on out, we're going to use this syntax to describe function. So this is exactly the same as the previous line. We're saying this is a function that take the type of max is a function that takes in something of types a and a. So the first parameters a the second parameters a. So it's the same and it returns something of the same type. Right. That's exactly these two lines are identical. Right. This is just a different syntax to write the semantics of what we want here. Any questions on this, the syntax? Cool. So could. Well, okay, we're gonna do it. Hello. Okay, but is max, right? Is max the best function we can write here? How general is max? This function. So it has to take in the same type. Right. So X and Y must be the same type, which makes sense. Right. I mean, you wouldn't want a max function that takes in two different things and tells you which one's greater unless you have some way to compare them. Right. What specifically has to be defined for these operators? What was that? Compare the less than operator. Right. Here, we're limiting the applicability of this function. Right. To say that well X and Y have to be able to be used by the comparison operator. Right. Maybe that's not applicable to all types of things. And maybe that's not very general. Right. We, the program are implying our own ideas over what is max. What does max mean? Right. So to be more general, rather than have us, the programmers hard code the comparison function, wouldn't it be better if the programmer could pass in the function they wanted to use to compare? So if they wanted to use less than, if they wanted to use less than or equal to, if they wanted to use string compare. Right. Now we've created a more abstract function. So what would this function look like? Well, we've had the same function max, but we'd add another argument called comp CMP. So based on what I just described, what do you think the high level type of comp is going to be? A function. Perfect. Cool. So then we can change our max function and write a function that says, Hey, if compare X and Y, then return Y, otherwise return X. So what's the type of this version of max? It's a function that takes in. So how many parameters does it take in three? So what's the type of the first function? All right, the type of the first parameter is a function. What is the first parameter of that function? How many parameters does that function have to take in? Two parameters? What type? T with the second type? What does it return? Boolean has to return a Boolean Y because it's used in a comparison operator. Right. So yes, you could think T T returns T is possible, but it's not based on the usage here. Right. The usage because this comp function is being called in a Boolean condition, it must return a Boolean. That is the constraint based on the usage here. Right. And remember right now we're building up our intuition here. We'll see how to calculate this, but we need to be able to look at these examples and be able to know and think for ourselves about what, what is the right types here. So the first parameter is a type that is a function that takes in type T and returns a type Boolean. What's the second parameter to max function? T and the third. What is the return? T. So are these important? So could you write? So he said, so it's a function and the first argument is what? A function which takes in a T and a T and returns a Boolean. And so the second parameter T T and returns what? T. So I said before that the T's don't matter, right. It doesn't matter what letter we use. So could I change these T's to A? Yes. No, why? Yeah. Yes, based on the usage of compare here. Right. We pass in X and Y. So comparison must accept whatever we have the type of X and the type of Y, which in this case is T. So what is the difference between how do we interpret these two function, these two types? What are the difference here? Are there any difference? There's more restraint on the second one. So what is the restraint? Yeah. So the function that we pass in must be able to accept the types that we pass as the second and third argument. Right. This way this says the first argument is a function that takes in any two types. Right. They don't have to be the same as X and Y, but that does not type check based on the usage here. Right. Compare is passed in X and Y. Therefore comparison must be able to accept X and Y type. It can't be any unconstrained type. So that's less general. Right. It's more restrictive because of the constraints on the usage here. Cool. Right. So we have a function of a function of t t returns Boolean t t returns t more succinctly. So this is why I like the succinct syntax. Right. It's a lot smaller than keeping writing out function of function of cool questions on this CMP. Yes. So if you're just said what's the type of CMP by itself, you'd say something that takes in two of the same type and returns a Boolean. Yeah. When you talk about the whole thing though, right, it's very important that they're the same type because this is now an additional constraint. Right. You're saying that this function that you pass in better taken two parameters and those two parameters may be the same type as the second parameter that you pass to this function. Cool. So now I can do cool things like this. I can call max and pass in the less than operator. You have to believe me that in some languages, you can do this with the less than operator. Right. Cause if you think about it, is less than operator a function? How many parameters does it take in and it returns what? A Boolean. So does it type check with our program? Yeah. So it takes in two values and returns a Boolean. And are those two values integers? I mean, here they're integers, right? So does this invocation type check? Yeah. Yeah. Yes. Yeah. So by, so this is part of let's say language or API design, right? When you're designing this max function, if you make it general like this, it's awesome, right? Because I can call max with string compare, right? And it will return which, whichever one is compares to be greater, right? So the function max itself, right, is more general. But now you have part of the problem is the type system does not contain those semantics, right? That the first parameter should be a less than comparison for this to make sense. So that would be a part of your additional documentation about this function that, hey, here are the semantics of this function that you pass in if you want to use this max behavior. If you pass in mod or you pass in, let's say, equals or greater than or whatever, right? It's going to return whatever or something completely different. Cool. Any other questions here? So this is really important. This is going to be something that I've tried to harp on a lot because it takes a little while to wrap your brain around passing functions into functions. Yes. Technically, not in this language, let's say. Yeah. Could you pass max into itself? Does that type check? What's the type of max? This is the type of max, right? Max is a function that takes in three parameters. So can you try to pass max here as the first parameter where how many parameters do this expect? Two. So it wouldn't type check. Cool. So then we can look at crazy things like this, right? So we can say function foo, pass in three parameters, a, b, and c, and we return the result of calling c with the first parameter of a bracket b. So what can you tell me about the types here? What do we know about the type of foo? It's a function. How many parameters? Three and return some type. So then what's the relation between those three types based on the usage here? C has to be a function. How many parameters does it take in? One and returns one, right? I mean, we can just think about it like there, number wise. No. In this language, we don't have return statements. Everything is implicitly returned. Cool. So what else do we know? So we said we know something about c. Do we know something about a? a is an array of what? Of type t. See how it gets difficult to just have all this? What do we know about b? It has to be an integer, right? So let's say that a is an array of t1, we'll call it. What does that then tell us about c? c is a function that has to take in a t1. And it can return. Does it have to return a t1? No, not based on the usage. So we can give it a new name. Let's call it t2. So here, I think we've got it all. So we have foo is a function which takes in an array of somethings, an integer, and a function that takes in that something t and returns some new type. But the key thing is that the function foo returns whatever c returns, right? Because it's being used right here. The return of calling c is being returned from foo. So we can have, we'll have this type here for foo. Yes. So does this say that the return of c is not a t? What does it say? They don't have to be the same, right? So there's no constraint relationship between the two. It could end up that they're both ints, right? They could all be ints here, right? But it's saying they don't have to be, right? You can have a function that takes in an int and returns a real for c, and then foo would have to return a real. Cool. This is fun. This is actually one of my favorite parts of this class. Actually, a lot of these are my favorite parts, so maybe I shouldn't say that. But I really like this stuff. So now we see a case. We have a equals 10. So what would be the type of a from this usage? Int. Just like what project? Fort, you're just reading my hands, you haven't read project four? I should have messed with you. Oh, I missed my opportunity. Cool. Then here we see that we have b as an array, and we're indexing int to b with c, and then passing the results of that, calling a. So what's the type of foo here? So what's the type of c? Int. What about b? An array of some things, and what about a? So it's a function. What does line one say that a has to be? What does this say that a has to be? Can a be a function and an int? No. No. It does not. Maybe, but not in the language we're talking about. I gave you guys too much knowledge. No, function a does not return an integer because a is an integer, right? Line one says that for this to type check, the constraint is a must be an integer. Right? Line two says for line two to type check, a must be a function. Something cannot be simultaneously a function and an int. Like we said, we can't have two types for the same things, right? Even casting it. No, type error. It's an error, right? We have two types. We can't have two types for the same thing, right? Okay. So this gets us to where we want to go. So this was building up our thought processes and our ideas about how to show that, yeah, we actually can think about and reason about these types, right? And try to assign the most general types that satisfy the constraints of our program. So Hindley Milner type checking is a general type inference approach. It's very cool. It infers the types of all the constructs that are not explicitly defined. So you can add types if you, the programmer want, you can say, I know this is an int or you cannot and let the compiler figure it out. And it's all about constraints. So this is why this is so similar. So basically in project four you're doing a very limited form of Hindley Milner type inference. You're going to learn how to do it by hand, the kind of full blown thing, but you'll notice in project four does not have functions, right? It's a much simplified, no arrays. It's a much simplified version of this type checking. But the entire point is that for every, you start with completely unconstrained types, right? You say, I don't know what A is. I don't know what B is. I don't know what C is. They could be what anything. And then based on their usage, you apply constraints so that you say, aha, A has to be an int. And then later you see, wait, A has to be a function. A cannot be both a function and an int. We have a type error. Cool. So this is essentially the process is we're going to apply these constraints continuously. And so for languages that use this, I highly encourage you to check these out. Super cool languages that use Hindley Milner type checking. And remember we're doing this statically without executing the program. Cool. Okay. So we first have to define our constraints. And these are the constraints that we're going to use on homeworks and midterms that have Hindley Milner type checking problems. So the first is constant integers. What are the types of constant integers? Ints. They have to be ints. We can also have constant real numbers. What are the difference? A decimal place. Perfect. Look at you guys. We can have constant Booleans of true or false. We can also have constant strings. Right? We can have strings like foo or bar. And these will be a type of string. Cool. Sweet. So we have different operators in programs we saw. So we have relational operators where we have a, b. And we'll see that a and b don't necessarily have to be variables, as we'll see they can be complex expressions. They can be relational operators of themselves. We could say a less than b greater than 10. Wait, could you do that? No, you can't. They could be expressions, though. They could be a plus b less than c plus 10. Just got a trouble there. So how we're going to do this, and the way we're going to do Hindley Milner type checking is we're going to operate on trees. So we're going to represent this basically as a parse tree. Right? So here we have the operator as a node in our tree, and it has a left child of a and a right child of b. And if we just see this relational operator, right, we don't know what the types here are. So we're just going to give them new type names for right now. And we're going to say T1, T2, and T3, right? Some brand new types that we don't know what they are. But what do we know about them based on this relational operator? Somebody want to give me one? Yeah. Yeah, T2 and T3 must be the same, right? If we're going to compare two things with a relational operator, they should be the same things. That's going to be a constraint in our language. Oh, shoot. All right. What should a relational operator return? Boolean. So which type in here is the return type of this expression? T1 because you saw the thing. Right? So these are the constraints that we know. T1, the return of a relational operator is a Boolean. It has to be a Boolean. And so if we have a constraint before that says, hey, this Boolean is actually should be an int, we would say that's a type error because it cannot be able to Boolean and an int. And we also know that type T2 must be the same as type T3. Cool. We have other types of operators, right? Arithmetic operators. We represent these the same way in our tree. We have an operator. We have a left operand and a right operand. So the operand can be either plus, minus, multiply, or divide. So what are the constraints here? T2 has to be what? T2 and T3 have to be the same. So in our language, yes, we're going to say that they have to be the same. You cannot apply addition to an int and a float or an int in a real, sorry, or an int in a string. They have to be the same. What else? Yes, T1, what they return, adding to int returns an int. Adding to real returns a real. In this language, we'll say adding two strings returns a string. It's going to be string concatenation. We'll say that minus and multiply and divide are also applied to strings, but I don't think you'll ever see that, so you don't have to worry about that too much. We'll say that it's there because it follows our constraints, right? Based on this language, the constraint is the types must all be the same. Cool. What about the array access operator? So just a second, I want you to think about if we are representing arithmetic operators as functions, you can think about them kind of as functions, how many parameters do they take in? What do they return? How many, they return one thing, and what do these constraints say about all those types? They have to all be the same, right? So you can think about arithmetic operators as functions, but take in two types and return the same type. Similarly, the relational operators take in two types and return a Boolean, just like we saw, with the less than operator with the comparison in max. So thinking that way, what would an array access operator be like? Taking an int and return, well, just taking one. So if you want to access an array, what do you need? You need an array and an index, right? So it's a function that takes in two parameters, the array you want to index into and the index. So what does the type have to be of the index? An int. Are there any restrictions on what the things are in the array? No, but what's the return type? It has to be whatever is in the array, right? The array is made of type T, the return type of an array access operator is going to be T, right? So you can think of this as a function, right? Takes in an array of type T and an int and returns type T. So that's what we have here. So the array access operator, so T1 is going to be the return type here. The left branch is going to be A, the array, and the right branch is going to be B. So here does it matter which is the left or the right branch? Yes, very important. I believe, no, is that true? I don't know. I'm not going to make any promises. Okay, I'm going to stop talking. I mean not all the way. Okay, so then what constraints based on what we just said do we have here in this diagram? T3 has to be an int. Boom. What else? T2 and T1 are the same? Why not? Somebody prove it to me. Yes, what we made depending on whatever is inside the arrays, right? So T2 has to be what? Can it be anything? Can it be a function? Can it be a structure? It has to be an array. An array of what? T1s exactly, right? So T2 is an array of T1s, right? This is important. This explicitly says you cannot apply the array access operator on a function. That does not make sense, right? A cannot be a function. A has to be a type array of something. And whatever that operator returns is that something. So this is why it can be an array of array of array of ints and accessing that. You can think of it as getting rid of one of those arrays and it returns an array of array of ints. Is that right? T3 is an int, right? So these are the rules we're building up in our language and that every usage here we're going to apply these constraints in order to infer the most general types here. Cool. Now what about function application? So what does application mean? Yeah, I'm calling it, right? I'm calling some function foo. I'm invoking some function. This is to separate it from definition, right? I can define functions that's different from calling a function, right? Cool. So in my graph, so I'm going to have some node either called apply or call. It will be very clear, right? So this, I'm going to call it r. This is going to be the return type of the function, right? And so the left most, so now the children, so the left one is going to be foo, the actual function, right? That we're going to call. And then we have t1, t2 all the way through tk of the parameters. So once again, does order matter here? Yes. The orders of the children are incredibly important in this function invocation. So what do we know? What constraints do we have here? Number of parameters of what? Using the types that I've defined here in parentheses. There is no t. There's a lot of t1s through tk's though. T of i is what? What is the type of the parameters? It doesn't matter, right? So we actually have no constraints on this t1 through tk, right? Just based on this, right? We don't know these. And the important thing to remember here, right? These nodes of this xk, x1, these could be their own complex tree, right? This could be a a plus b, right? Or it could just be a variable name. It could be whatever. But just because it's passed into a function, that does not give us any information on constraints of that parameter. Yes. Yes, we will see that later. But just based on the usage here, right? When we see this function invocation, we cannot say anything about these types t1 through tk. But what can we say something about? What was that? f and r should be equal? Same. r is the return what this function returns. Yes. So f is a function, right? This says that foo has to, well, this is where it gets tricky. Again, foo does not have to be a variable. Foo itself could be its own tree of something. Think about an array of functions. And then I index into that array, that's going to return me a function. And then I can call that function with some parameters. But I know this node f right here, this node must be what? Must be a function. And what do I know about that function? Has parameter types t1 through tk and what does it return? Returns type r. That's it. That's all I know. This is very important, right? Because I know that f has to be a function. I know it can't be anything arbitrary. It can't be a pointer. It can't be a structure. It can't be an int. It must be a function. No, because we want to keep distinct the foo itself and the return of calling this function with x1 through xk. So this is how we're going to draw it. Because then you have to make the distinction of what's foo and what's the return type of foo. Because I may know based on this usage, right? I could have all of this, this function invocation plus an integer. And so that would tell me that this return type r must be an integer, which means that f has to be a function that returns an integer. Then we have our function definition operator. So here I have function foo is equal to x1, x2 all the way up to xk. So I'm defining now foo has to be a variable, right? It's not going to be something complicated. A function definition, we have to have a name. So we're calling it foo. We say we have parameters x1 through xk. And expression here is the body of of the function. And as we said, right, there's no explicit return value. So we could say whatever the type of the expression is, that's going to be the return type of foo. So the way we're going to do this is functions. So we're saying we have a function definition on the left. So this is where we get a little bit weird with the notation. But it's fine. We have foo, which is type f, x1, which is type t1, x2, which is type t2, all the way up to xk is type tk. And then we have type e, which is the return type of this expression. This expression could be crazy complicated. It could be if statements. Well, I think we'll see if statements in a bit. If statements, array access operators, function calls, all that stuff. So what are the constraints that we have here among these types? Which is what? What does that mean? Based on the types here. So you can't use foo to talk about types. What's the type of foo? F. So what do we know about F? Can it be any type? Can it be a pointer? It has to be a function. Yeah, that's the first thing we know. F has to be a function. How many parameters does F have? K, what are the types of those d1 through tk and the return type is what? E. That's it. But just based on the definition of a function itself, we don't know what these types can be. There's no constraints. And the other important thing is these are all distinct types. There's nothing here about a function definition that says that t1 has to be the same as t2. Because that's the most general thing possible. There may be constraints in the expression that then says, hey, x1 and t1 and t2 have to be the same. And we'll see how we can handle that. So we'll see how we apply all these things, but we need to define these ground rules of what are the constraints here. Oh, it doesn't return anything in our language. So we're saying that fun does not return anything. Assignment? No, this is a function definition. I'm just going to say it's special. Yes. Yes. You cannot set that equal to some parameter. We will never do that. There's some variable. You could do that. We're not going to do it here. Yes. So like in JavaScript, you can create an anonymous function, a function that has no name. And you can assign that to a variable. So you can say var foo equals this anonymous function. And then you could invoke foo. So you could think about doing that here. We don't do that in this language. So I'm not going to go into that. That'd be an interesting way to change it, though. Maybe I'll do that in the future. We'll see. Okay. So with an if expression. So we have if condition then expression one, otherwise expression two. So here in our tree, we're going to have the if have let's say t four, and we're going to have the condition is t one, the expression one is t two, and expression two as t three. So then what are the constraints here? T one has to be a boolean. And once again, it does not have to be this node. It can be a complex tree. T one must be a boolean. What else? Damn it. This is the problem. I don't have presenter mode because I don't know what's coming. So why do t two and t three have to be the same thing? Say that louder because they're in a conditional statement. So at runtime, we could take one of these two paths. We don't know which path we're going to take. So the return type better be exactly the same no matter which path we take. Right. Otherwise we'd have the return type here having two types, and that would be not good. So we need t two and t three to be the same. And as we said, expressions return things. So an if statement returns either expression one or expression two. Therefore the return of an if expression is t four. So t four has to be the same as t two and t three. So those are our constraints here. Well, yeah. So like the tertiary, tertiary operators that the way I don't think I'm saying that right. The ternary, the question mark with the colon, right in C. It's basically like if this then this else this, right, you can use that an expression, you can assign some variable foo equal to this thing. So that returns something. It's just the same here. If this condition then return expression one, otherwise return expression two. The return type of if does it do well, the condition must return a Boolean, which is what we're saying here. Right. So t one has to be a Boolean, but an if statement, just like a ternary operator or whatever, right returns either the true thing or the false thing. Yeah. So we're looking at this slightly symmetrically different than if the condition execute. Yes. Yes. Or you can think of execute and return and return the last statement is kind of essentially, I think we'll only have like expressions like this will be a tree. It will never do multi-line things in this Hindley-Milner language. So we don't have to really worry about that too much. Cool. Okay. So what we have to do is we have to propagate all these types and all these constraints, right? We have to see if these constraints can be satisfied. Am I using a variable in the same place as an int and as a function, right? That would be a type error. Do I get into an infinite type? Right? Do I say some type t one is a pointer to a t one? Right? That can never be a type because you can't have a pointer to something to a pointer to something to a pointer. You can't have something be a pointer to itself. Right? You can define something and you can find a pointer to that thing, but those two types are distinct. Just like we talked about, you can't have a function that takes itself as an argument. Because how would you ever define the type of that function? Then what's the type of the second argument? Then what's the type of that second argument? What's the type of that second argument? Right? All your types have to be finite. So the basic idea is simple. We're going to do this parse tree of the program. And to make your lives easier on all exams, you will be given the tree. So you don't have to think about it. That is how we eliminate all ambiguities. There's no parsing differences because the tree is the tree. That's what you use. Then every time you see a construct with unconstrained types, create a new type. Whenever you see something that you haven't seen before, you give it a new type. And then if you say, okay, this node has to have type t1 and it also has to have type t2, then those things must be the same. And you give them the same type. So let's walk through an example. So we have here a function foo, which is ab and c. So we have a function definition. I use foo fun before we use def. That's fine. So now I'm just numbering the nodes. So this will help me when I calculate this by hand. So my definition, I have foo, I have an apply, I have c. So now this is the right-hand side. So this says that first the function application, well, not first, but the function application happens, the function that's getting called is c. And what gets passed in is the result of an array access operator on a with b. So I have numbers for all of the types in my program. Sorry. I have numbers for all the nodes in my program. Now I need to go through and I need to have types. So I know I will have types for foo, ab and c. Those are my variables. All variables have types. And each of the nodes in this program will have a type. I think I may have made a mistake with one in here since we said earlier that that doesn't return anything. We'll see how this plays out. Cool. So I'm going to keep track here for all these things. What are their current types? Cool? Yes. Because I didn't change the syntax at some point. Cool. So I know just from looking at node one, saying this is a definition node. So I know that I have some new, I see I have a function foo that has parameters ab and c. So I know I can give ab and c new unconstrained types. A function definition does not constrain the types. The important thing is to focus yourself just on one aspect here. So I'm only looking at, I can't do it well here, but I'm only looking at this this first node and it's immediate children. And I'm applying those constraint rules that we already talked about. I don't look at all of this. I don't look and cheat ahead and say, ah, b, I know b has to be an int. So I should just write int. We'll get to b. We'll get to this node. First, we're going to look at node one and only its children. That's it. Does the order matter? No. You could go bottom up. You could go top down. I like to do top down. To me, it makes a lot more sense. What is the purpose of the numbers again? So that we can do them in order. So I can say right now I'm looking just at node one and its children. So from that, I know I have ab and c who have some new unconstrained types I've never seen before. T1, T2, T3. The important thing here is these are distinct types. They don't have to be the same yet. And I know that foo, foo cannot be any type. Foo must be a function. And it's a function that takes in three parameters, type T1, T2, T3, and returns some type T4. The return type here is some new thing that I've never seen. But the arguments to foo are the same based on those constraints that we just went over. We know that the parameters ab and c will have the same types as the types to the parameters of foo. Make sense? What else do I know here? Yeah, so the other reason why we number things is so we can talk about them, right? So node two has to have the same type as what? T4. So I can put that in that table. I know the type of node two. It's got to be T4, right? Cool. So now, now I've done node one. I've applied all my constraints. Did I violate any previous constraints? No. Right? I haven't. Then I look just at node two. So I already know the type of node two itself. I don't have to create a new type. I know the type of node two. And then what does this tell me about nodes three and four? Node three must be a function, right? What about node four? Node four has to be the function parameter to node three, but we have no constraints over node four. And specifically, we don't know anything if it's the same. We don't know for certain whether it's the same as T1, T2, T3 or T4. So therefore, we have a new unconstrained type T5. So create a new type T5. Assign that to node four. And now what do we know? So what do we know about node three? It's a function. It takes in what? How many parameters? Two? How many parameters is this taken? C. This function here, just remember, just looking at this, right? We don't care about any of this right now. All we're looking at is node two, three, and four, right? And based on our syntax, the applied node, the leftmost node is the function. And all the nodes after that, all the way to the right are parameters. So how many parameters does node three have for its type? One. What is the type of that parameter? T5. What's the return type of this function? T4. See? We already know that just from here, just from looking at this. We know that node... So we know the return type of the type of node three has to be the same as node two. So we know that node three is a function that takes in a type T5 and returns a type T4. That's very lazy here and didn't put the parentheses. You should always put the parentheses. Makes it a lot easier to read. Especially when you have functions that return functions that return functions. Cool. Why didn't I say anything about the type of C? Because here I'm only looking at node two and I'm treating node three and four like I don't know what they are. I'm not looking at them to see what they are. I don't know what constraints they apply. Because node three, just like node four, could be a whole tree. It doesn't have to be a single entity. So I'm going to wait until now I've applied all the rules in node two. So now I look at node three and what does node three tell me what constraints do I have? T3 has to be what? A function that does what? Takes in T5 and returns T4. So everywhere here that I have a T3, I can get rid of type T3 now. Because T3 is too general. The more specific type is a function that takes in T5 and returns a T4 and I can change that everywhere. Cool. So I've looked at node three, then I look at node four. And what does node four tell me about nodes five and six? What does it tell me about node six? What about node five? It's an array of what? T5. It's an array of whatever node four is. And node four we know is type T5. So we know node five has to be an array of T5. And node six has to be an int. Then we look at node five and what does node five tell us? What constraint does it give us? Node five, this node. T1. A is a T1. So this says T1 has to be the same as an array of T5. Which one is more specific? An array of T5. So we can replace everywhere T1 with an array of T5. I'm getting a little bit off. What am I doing here? What's happening? I can't tell it's changing. Oh, just a new line? Okay, whoa, sorry. Okay, then we look at node six and what does node six tell us? Oh, it's already there. Okay, now what does it tell us? That's what was changing. I was going crazy. T2 must be an int. Which one's more specific? Int. So we can replace every T2 with an int. And now we've gone all the way through. So we don't need to iterate this in this version that we're doing here. We go all the way through and now we know all the types for all the variables in this function. So we know that foo is a function that takes in an array of T5s and int and a function that takes in T5s and returns T4s. And all of this returns a T4. I'm going to change this because this syntax looks better to me. Wait, what? Oh, this is going to be annoying. Success. So just by applying those rules, remember, the key is apply those rules locally. Node by node, all the nodes in the program. If you ever get to the point where you say that something is an int and a function, it's an error. Yes. T4, yes. T4 and T5 could not be the same. Why is that correct? Wait, oh, they could. Yes, yes, yes. There's nothing that says they can't. Okay, perfect. Yes. This doesn't say that they can't be the same thing. It says that they're not constrained. They don't have to be the same thing every time. Right? So you could pass into this function an array of booleans, an integer, and a way to change booleans to strings. And this whole thing would return a string. This is what this says. Cool. So I think, so I need to keep you long so I could go over this. This is going to be on the midterm. And so what I'm going to do, I think I'm going to release homework seven very soon so you can have some practice, but it won't be due till after the midterm. The homework seven will be just Hindley Milner type inference. So you can get practice for that through the midterm. There will also be a practice midterm out later today. So keep an eye on the mailing list for that stuff.