 Cool. Good morning, everyone. I miss being here with you. That's cool. So I hope you had a good class on Monday. You can tell me about it after. And then I hope you watched the lecture because we're done with kind of basic types. And now we're going to go into more complex and interesting type inference. So specifically today, we're going to talk about a Hindley-Millner type inference, which is essentially a much more advanced, or there's two ways to think about it. Either you can think about what you're doing is a smaller, scaled-down version of Hindley-Millner type inference. Or you can think of Hindley-Millner type inference as much more expressive and does more things than what your project floor is doing. But either way, they're related, so that's cool. Can we ask questions about the project? We'll save it for after. Let me make sure we cover this. Questions on types? Any questions? Okay. So we looked at the type systems, right? So all the type systems that we've seen so far, how do we know the type of a variable? Everything we've looked at in the type systems that we studied, how do we know the type of a variable? It's given. What was it? It's given. Who gives it? Me? Well, kind of. Yeah, the programmer, right? So exactly. So the programmer is explicitly declaring the types of the variables, right? They say, I want a variable foo and it has this type bar, right? So we've seen that with all kinds of examples. So we've seen examples of the arrays where we give the range of the array and the elements of the array, what type they are. So, well, what's the name of this type of a? Int square root. The name of it? What's the name of the type of it? Yeah, what's the name of the type of a? Integer. So let's think about this separately. So what is the type of a? Int. It's an array of ints. It is an array of six elements of int of integers. But what's the name of that type? If I say it's a foo a or something, where foo is some programmer defined type, then what would be the name of that type? Exactly. So does this type have a name? No, right? This is an anonymous type. This is a type with no name. We are not declaring that there is a type called array zero to five of int, right? In our type declaration. This is a variable declaration, right? So that whole statement isn't our type. Correct. Okay, because this is a type constructor, right? We're constructing a new type of array based on some of our basic types. You could say that the array has a type because the array is of type int. The specific array that we're talking about here has range zero to five and hold ints. Yes, you could say that. But specifically about the name, right? So why is that important? Why are the names of the types important? Type equivalency. Name equivalence, internal name equivalence, and structural equivalence, right? Okay. So here we're just having int i, right? And then we know that now, once the programmer has defined these types, now we see their usage in the program, we can say whether they're correct or not. So is this, would this type check and why? Yes. Yes, why? Because i is an int. Because i is an int, so why does i have to be an int? Because it's being used in the index of an array which is, is to be an int. Which must be an integer, yeah. And at the output of any index of an array of zero through five of type int defined as or named a is the output of an int. So setting it equal to an int is correct. Right. So you have this array operator essentially, think about the type system returning something, right? Returns this type int. You're checking that this int here is the same as this type here which is an int, right? You're saying yes, that type checks. Cool. And so now we know, right? So if we have a different program where i is declared as a string type, right? If we were to do this, would this type check? No, why not? Because the index i is not the string isn't going to return something valid most likely. Right. So the reason is the types, right? So the type of the bracket operator means that to do an array access, we access arrays based on their element, right? Not on strings. Yeah. If you switched i to a char, would it try and convert it to the integer value of it or would it just crash out? I don't know, what do I imagine? Definitely depends on the language. Right here we're talking a little bit more, not like concretely in C but kind of more in this language we've been talking about. Where the type system, we kind of, I mean we want it to be very strict, right? So you could actually maybe, so let's see, if you knew the range of the array, right? And you saw a character in here. If the range is bigger than a character then you could say that that doesn't type check, right? Because a character can't describe all possible indices. That would be interesting. That's what you can do when you know the size of every array. So you could do some cool tricks like that. Okay. So can we do something like this? Int a and int i and do ai equals the string testing? No. Why not? Because you're trying to load a type string into an array that contains ints. Exactly. So here we have, we have a string so we're trying to set a string equal to the return of the array access operation, right? So in all these types what's the return of the array access operation of a? A memory location for int. Int types. So something about memory locations, so just give her that an int, right? What is inside of this array, right? The array access is giving us one of the elements of this array. So it's essentially peeling back all that other type. So we're checking that is int equal to a string? Well no, of course not. So at this point in your careers I think you're familiar with some more expressive type systems than what we've seen here, right? So what are parameterized types? What does that mean? Right? So in some language, so like generics in Java or templates in C++, right? What does that actually allow you to do? Yeah, exactly. So you can actually use, this allows you, so the parameterized, right? So it's essentially like the type is a parameter to either a function or a class. So instead of defining a function on two integers, right? You can define a function on two types that are the same, right? Which is actually more generic and that means that function can be used in more scenarios, right? And it allows the programmer a lot more abstraction and power in defining these more generic types of functions. And so the idea is the type, the type is given as a parameter to either the function or the class. So Java and C sharp will have generics, templates in C++ both do this. And so let's look at an example. So you can write a function in Java. So here we're going to create a static random variable. We're going to have a static method called choose that is going to take in two parameters first or second. And the idea is this function will randomly return one of those values, either the first or the second. So you can think of this as a handy function where if you just want to randomly choose one of these two elements, this function will allow you to do that. So without parameterized types, you would have to specify the exact types of each of the arguments, right? You would have to say first has to be an int and second has to be an int. But really, from what this function does, we just want it to return one of the two. We don't care what the types of those arguments are, right? We only want to just return one of them. In fact, in this, the types are completely irrelevant, right? As long as they're the same, right? So why do they have to be the same? Exactly, we're only outputting one, so the function that calls us, right, needs to know the type of whatever we're returning so it can properly manage the types, right? So if these were two different types, then we just returned some random type safety would go to heck. So we can do this very easily. We can have a little function that calls random dot next int. It gets an integer, it mods it with two. So mod two is going to return either zero or one, depending on if it's odd or even. And if it's zero, it'll return first. If it's one, it'll return second. So now we have this generic method that we can apply to any types, any parameters. So we can, in our function, we can have variables x and y, right? Which are integers. And then we can print out chooser dot choose x or y. Didn't I say that the parameters here to, the types have to be past as parameters? So did I specify any of the types here? No. So how does the, how does Java type check this? It would look at the angle bracket. So from the invocation of this function, right? If I wanted to specify exactly the parameter of the type, could I do that? I think you had brackets. I can't remember here, here. I think you had brackets here. I think the parentheses. Yeah. You had brackets here to specify exactly which type you want to do. Or does it know because both of them are of the same type. Exactly. It just figures it out. It can look here, right? And it can say, okay, I'm calling a parameterized function. Both the first parameter and the second parameter are of the same type. So r, x and y of the same type. Yeah. Yeah. And what is that type? Int. So it knows to invoke this with past using int as the argument. Yes. Is that true for all generics in Java that they'll figure, it'll figure it out. It'll figure it out. Part of the problem. That's why it's not as smart. So we're building up. So we're going to see that Hindley Milner is much more powerful than this. But this is kind of what you can use in Java, which Java is kind of getting there. These other languages are getting there. So you have strings. So I can call the exact same method with strings, right? And Java has enough information at this function invocation to know exactly which type to. Okay. Go ahead and do that. From the choose method, what type am I returning? You're returning int. T. I'm returning a t. Whatever t. What is t? Whatever you pass it. Whatever is that parameter of this method. Whatever the type of that parameter of this method is. Right? So in this invocation, this type parameter is int. So it's going to return an int. T is going to be an int. When we invoke it here, it's going to be a string. And it's going to return a type of string. Yeah. Do we return type every different than the parameters? No. That's exactly what this constraint here says. This constraint of this method says that the return type is always the same as the arguments. So you can think about, essentially one way to think about it is every time this function is invoked, it looks and sees, well not exactly why it's invoked even beforehand. It can see that, okay, this is on int. So I know t is an int. So I can make a whole new copy of this method replacing t with int everywhere. It's just a regular Java function. And then with this, the same thing, I know here t is a string. So I can create the exact same thing, a brand new copy of this method replacing all t's with strings. So then that makes the parameters have to be the same type also. Yes. It can never be different. And that's defined here. So you can use generics to have as many types as you want. You can have t, u, v, whatever in this list. This defines the parameters. But where those are used, that defines the relation between the types. You have to use this when you use an array list or... This is exactly what's happening. So because an array list or any kind of list, you don't really care what types you're putting in. It doesn't affect anything. But you want that class to be specialized on that type because you want to be able to put in, let's say, strings in one array list and ints in another array list. Not a great example because of the primitive example, but the head of the list is the same. Okay. So in this case, we have what's known as explicit polymorphism. T is the programmer is declaring the parameterized types explicitly. The programmer is explicitly saying, here's a function, and here are the types to that function. It's going to take in a type t in this example, so one type. So it's important that this is a little bit of different polymorphism than what you're used to thinking about in the object orientation. Here we're thinking kind of on functions and types so that I can use the same function in different places just like with object polymorphism. I can use, I can call a parent class and depending on the specialization of that class, it may call a different method. And so this is great because it allows, you can call a function or a class with different types while still checking type compatibility. So you still get all the guarantees of the type system. The type system will ensure that everything is still good. We still had to say this type t, right? Just like in the previous things we looked at, we have to explicitly declare the types of variables here. But let's say if I got rid of this t and didn't specify any types of this function, could you come up with the types that this function should have, that the type insurance here, that each of the parameters have the same type and the return value has the same type? Let's think about that. And so this leads us into where we want to go, which is even more powerful and very cool, where we as the programmer don't even want to specify the type parameters. So we want to be able to not even specify any of the type parameters, and we want the system to essentially infer the types that we want and create the program accordingly. So dynamic language is right. In a lot of dynamic languages, do we have to specify the types, so in Python or JavaScript or Ruby? Do you have to specify the types of the variables? No. I think we talked about this a little bit before, right? But do you think it's better or worse? Worse. It's more complicated for sure. Worse. Yeah, it can be worse in the sense that you don't know when you run the program if it's correctly typed or not. You could be trying to add strings with lists or something, right? Whereas with a compile time language like Java, you don't have that. But there can be a lot of cases where have you ever tried to make changes to your program? So like, oh man, I was using this string, but now I want to, instead of using a string, I want to change that with a struct or an object, right? So you have to go through in every place you use that variable in every single parameter, every single function, you need to change that to now use this new type. Otherwise the compiler won't let you, won't let it work. And the other nice thing about Java, I mean, some things about Python that are nice is you can, they use kind of what's known as, they call it like duck typing. So rather than, so basically the idea is if it walks like a duck, swims like a duck, and quacks like a duck, then it's a duck. What this means is I don't care about object hierarchy, parent children, whatever. If I get an object, and I call a function on that object, if it has that function, then I'm happy, right? I don't break the type system. And if it takes the same parameters and everything. So this is nice because you can write very generic functions. You can have a function that, let's say sorts elements, and as long as it knows how to compare things and pass them to some kind of compare function, then it can properly do the sorting, right? So in that sense, you can have functions that are a little bit more generic, but you have to give up the fact that you have this nice safety net of static type checking. So what's super cool about what we're learning is with implicit polymorphism and with Hindley-Millner type inference, you kind of get the best of both worlds. So the type system is going to actually statically check and verify that the types of your program are correct while at the same time the program does not have to specify any of the types. So the program is going to automatically try to infer the most general type for every single construct, function, function invocation, everything in the program, expression in the program. So if it can't solve this, if there are no types that solve this, then it will say there's a type error. Otherwise it will figure it out. And it's actually very nice because you as the programmer, you don't have to specify the types. You can trust the compiler to figure it out what you meant that things are in search strings. And then later on, if you want to change that, it makes it a lot easier. We'll also see it allows you to write functions that are a lot more general than what we're used to. Let's look at an example. So before I get started on this, this is the principles of programming languages class. Programming languages is one of those words, or two of those four words, I guess. So we've been talking about a lot of different kinds of programming languages. So here I'm going to introduce some kind of new, it's more of a functional programming language syntax. It should be fairly clear. I don't think it's not meant to be tricky. It's just meant to be easy to look at and discuss because this is the way that a lot of, a lot of these languages that do implicit polymorphism how they look. So it's just easier to kind of, and we can talk about some of the differences there. So for instance, when we do have function definitions, we're going to use fun because functions are fun, right? So we're defining a function foo, foo takes one parameter x, and so what does foo return? Yeah, it returns the first parameter, right, x. So that's the definition here. So foo x equals x. So now, what is the type of foo? So do we specify any types here? No. But what can you tell me about the types here? Whatever x's type is. Because it has to return x. So whatever the type of x is. Right, okay, so let's talk about, think about this first. How many different types are there in this little function definition? Infinite. How many things do we need to... That's completely out of the spectrum there. Go bring everything. It's actually negative infinity. Damn. So how many things do we need to be able to say what the type is? Like, for instance, do we need to know the type of x? No. Well, the program's going to statically determine that, right? It needs to determine types for every variable to do type checking, right? So the programmer doesn't have to specify, but the type system needs to figure out a type of x, right? What else does it need to figure out types of? What was that? Foo. Yeah, we also have a function that we're defining foo, right? So we also need to know the type of foo. Actually, one of the coolest things about programming in a language like this is you can actually, while you're programming, you can ask the type system what exactly is the type of x or foo. And it will tell you what it thinks the type is. And if it thinks it's something really weird, that means your program is wrong. And so actually, oftentimes, so I've done some programming in OCaml, which is based on this. And it's crazy because, like, the type system, but then when your types all work, your program actually works, because it's actually what you meant for it to do. So if we were to do a type of foo, what's the type of function foo? Using kind of the type syntax that we've talked about before. So what could we give? The type of foo. Right, so what is the type of x? Anonymous. Is it undefined? Anonymous. Anonymous. Generic. Unspecified. Yeah, I think these are all the same things. We can basically say it's any type, right? So we can just make up a new name for it, right? We can make up some name t, right? So we say it's some type t. So the type of x is some type t. So then what does that mean about the type of foo? In x is type of t, then foo is type of t. But foo is a function. Is x a function? It's a return of type of t. Exactly, right? So foo is a function. And what do we know about types of functions? What do we have to say about the type of a function? Just return type? Input types? Is that the word for that? Parameters. Parameters, yeah. So the types of the parameters and the return type, right? Exactly. So if we're to use this, foo is a function that takes in what? Type t. Type t and returns what? Type t. Type t. We can know this just by looking at that. So basically this means, right, whatever we invoke foo with as the type, whatever the type of the thing that we invoke foo as, the return type is going to be that same thing. So if we call foo with a string, we're going to get back a string. If we call foo with a list, we're going to get back a list. If we call foo with a function that takes in another function, that takes in another function that returns an int, it's going to return that same thing. So it doesn't matter what we pass in. We know that the type that gets returned is always the same thing. So we're also going to change the way we write functions, types of functions. So what we're going to do is we're going to put all of the parameters of t in, sorry, the types of the parameters of a function in parentheses, separated by commas, and then we're going to have an arrow with the return type of t. So this means a function that takes in type t and returns type t. And this is just to, we can keep doing it like this. This is a little more succinct, is exactly the format that most languages that use this use. So it'll also help you use any of these languages. Okay. So let's say we have some function foo x that returns x. So our same function above. And let's say we have another function bar y that returns foo of y. So now what's the type of bar in foo? Can we determine it? So what's the type of foo? Does the type of foo change? So foo is a function of t that takes in one parameter type t and returns a parameter type t. We're going to write it as t, t. And what about bar? Yeah, so we can, we actually reuse the same terminology because what this means is that in this function bar, these types have to be the same. The input to t must be the output of t. But the relation between these two t's has no relation. They could just as easily be some other random value. So let's say we have a function max. So we're going to say if x is less than y then if we're writing a correct max function what should we return? So more things to notice about this function, this language. We don't have explicit returns. So we're not saying return y or return x. What we're actually doing is we're saying an if statement actually returns something implicitly. So the result of a value, so an if is just an expression that if its condition is true it will return whatever its true block returns. Otherwise it'll return whatever its false block returns. So now what's the type of max? Or maybe put it another way. Do you have enough information to decide the type of max? Why not? Because x and y may not necessarily I would say for this to work x and y have to work. Why? Because they have to be comparable. Okay, yes. So there's actually two separate issues here. So just like your homework we have x less than y I guess your project. What does that mean about the types of x and y? They must be comparable. They must be the same type. Comparable, yeah, that's part of the problem. We actually don't know if they have to be the same type. It depends on the language. But it really depends on what this... So is this less than operator? Can you think of that as a function? So if it's a function, what does it take in? What would its type be? Oh, a Boolean. How many parameters does it take in? Two parameters. We'll say it's the same type t and t. And returns what? So... But let's say this is a less than symbol that's only defined on integers. So it's int int Boolean. So what does that say about the types of x and y? They have to be ints. Okay, let's think about this from another perspective. Let's say this was some random function. We don't really care. When we first look at this, x and y could be arbitrarily different types. We don't have enough information to say that they're the same type or different types. But looking at this program, depending on which branch we go down, if we go down this branch, we return y. If we go down this branch, we're going to return x. So what does that tell us about the types of y and x? They have to be the same. They have to be the same, exactly. So these types must be the same because they're used in two different if branches. So another way to think about this, if you have an if branch, each of the types of the branches are the same. Because no matter which branch you go down, you have to return the same thing, yeah. So what about if you have an int? You mean for the less than? Yeah, I mean like if you said 5 is greater than 4.2. Yes, so it depends on the specification. So if we're assuming that the less than symbol is only defined on ints, then we're going to say that max is a function that takes in an integer and an integer There's nothing more general we can say about this. We can't say it's an arbitrarily type t because we see from the usage in the function of this less than symbol that they have to be ints. This is the most general. And so the other way we'll write that is int comma int arrow int. Let's change it a little bit. Is this really general for the programmer? No. No, it's kind of crappy, right? We've written this, what should be this max function, but because we, the programmer used this less than symbol we are inherently forcing this function to only work on integers, which is what they're less than symbol type. So we have to start thinking a little bit more higher level. So what, really what we want, so do we want to specify if we're writing a function for max? Do we, the programmer who writes this function for max, the comparator operator? Right, we want some logic to use a comparator function in order to decide which of the two values is larger. But this isn't very general. So why don't we let the programmer tell us what comparator function they want us to use? So how can we do that? By using it as an argument, right? So let's extend our function to have the programmer pass in a function to be called. So those of you that have used vectors or sets, right, in C++ you can tell it specifically what function to use to compare two elements of your set. Right. So this is extending this. So the idea here is functions are essentially what they call first class, right, we can pass functions as arguments, we can return functions from functions. We can treat functions, functions from any other variable type, right? They're just the same as strings or ints, just that we can invoke them, right, we can call that function. So now how do we rewrite that function that we just saw? Right, so what's the type of max? Or it's, let's see here. It's a function, right, so it's not going to just have one type. So it's... How many parameters is this type of max? H is of type T. The type of max is the type of compare. Type of max is the type of compare? How many variables... So, okay, let's think about this. So there's a couple of different layers in here, right? So we know just from looking at this the types of max and the types of compare can't be the same. Why? Two, compare takes two parameters, max takes three parameters. So max is a function that takes in, as its first parameter, a function that takes in how many parameters? Two. Two parameters and returns what? A boolean. What do we know about those two parameters? They have to be equal. Why do they have to be equal? Does anything about this function and vocation here say that they have to be equal? They have to be the type of x and the type of y, right? Because the programmer specifies the comparison function somewhere down the chain. Exactly, but let's look at this. Remember what I said about if statements? So what do we know about the types of y and the type of x? They have to be the same. And they have to be the same as the return type of max, which means that the types of the compare function must also be the same. So this is actually a function of... functions of t, t, which returns boolean and it takes in a t and a t and returns a t. So if we were to write this in our simplified format, we have a function that takes in at its first argument a function two types of that function and returns a type t. Do you know when you say in a function the parameter is they need to be the same type and the function should return the parameters, right? So do you know how that doesn't hold on the comparison function? You're passing in t and t, but you're returning a boolean. Sorry, I said that again. So you know how max well okay, a function, the parameters need to be the same. So t and t, for instance, and it returns a t. That has to hold for max. So for comparison, the parameters is t and t, but you're returning a boolean. Yes, so why? Where is the cmp function used? Where inside max, specifically? In the if statement. In the condition of the if statement. So what do we know that the condition of the if statement has to return boolean? Boolean, right? I guess we didn't explicitly specify that, but we'll see that. We need that to be a boolean. So that's what we're saying is, if it wasn't used here, then there would be no constraint on the return type. The most general type would be some new type w that we haven't seen before. But because specifically of its usage right here, we know that it has to be a boolean. And if the function usage, if we use compare somewhere else, the return type of compare is not a boolean, then we know we have a type error. So you can think of we treat some really generic types, and then based on the usage, we restrict them to more specific types. So now we can do cool things, right? Now we have actually a very general function. We can call max and we can pass in the less than operator and then pass in 10 and 200, right? So we can call this function max with the less than operator. And we can also call it with the strain compare function to compare strings. Right? So the less than function takes in two integers, right, and returns a boolean, which now means that this function invocation here, these must be integers, right? These T's must be the same. And then we know that this function invocation must return an integer. Here we know strain comp is a function that takes in two strings and returns a boolean. So we know that the types of these two parameters must be a strings, and then the return type here must be a string. And we got all of this without specifying any types. So we got a lot of fun with this. So now we have a function foo. How many parameters does it take in? Three. And then it returns the result of calling C passing in to C's first argument the result of A bracket B. Right? So based on the usage here what do we know about the types of C? At a high level. C would have to be a function. C is a function, yes. How many parameters? One. What else? A is an array. And B must be an int. Right? We know from this usage, we can see here from this array access B must be an int. We can only access arrays with ints. So there's other things implicitly that we know here. So we know that the return type of C is also going to be the return type of foo. Right? We know those have to be the same. We also know that whatever A is an array of let's say it's an array of W's that must be the same type that C accepts as its first parameter. So we're going to have A is going to be some array of type T. B is going to be an int. And then C is going to be a function that takes in a T and returns a U, some new type U. And foo returns U. Actually even just from this type system we can kind of see a little bit what's going on. We can see that like okay we have some array of some type T's, we have some index into there and we have a function to translate from that type T which is the return value here. So we have some way to it's essentially translating like C is translating from T's to... So does this mean that T and U can't be the same? Not necessarily. Not necessarily, exactly. They can be the same. It doesn't prevent that. Cool. So this is in our simpler format. New function. So now we say A is 10. So usually we're not going to do it like this with the statements. We're very clear to read here. So A is a 10. We're calling A passing in B bracket C. So what's the type of foo here? So what's the type of... let's go in reverse order. What's the type of C? Int type of B an array of something type of A an int Oh I see. A is a function that returns an array of something in returns. So what does this function say that A is? Is it A? Sorry, a function. What does this line say? What does this line say that A is? A function. Can something be a function and an int? If you believe. If you believe hard enough? No. Type error. You cannot do that. Sure question. Yes. So Hindley Milner type checking is a general algorithm to do this for a program to automatically infer what the types are at all points in the program and for all variables. And this is amazing because you as the programmer don't have to specify any of the types. The type system will help you and decide the types for you. And the key idea is that it leverages it uses kind of like you think of it as like base knowledge, right? It uses these things that we were doing, right? Means that the argument to the array access returns some type I and that means the array that you're accessing must be a type array. It uses function calls so it knows that function calls, the types have to be based on the invocation. It knows how many parameters need to be in that function. And then it builds up on that and it builds these together to try to infer the types. And if it can't, if it ever gets to a point where it tries to say, oh, an int is the same as a function or an int is the same as a string that throws a type error. And so this full the full Hindley Miller type checking is used in OCaml, F sharp, Pascal, a lot of actually really cool languages that are fun to play with. So the key idea here is you first need the type constraints of your language, right? If you have no constraints then everything always type checks, right? So we first have constant integers, right? So we know what are the types of constant integers and we're going to call them ints. They're going to be type int. Constant real numbers. So when we have floating point numbers what's the type of those? Reals. We have constant Boolean. So we have true or false. Almost like you're doing a homework assignment on this. Right? Types Boolean and we have strings. Right? Where the types are strings. So constants, all the constants are specified types. We have our operators. So we have relational operators. So we have some operator which has some type t1. I'm going to draw in tree syntax which is going to be clear when we go through the algorithm. So we have our operator and we have the first the left side which is a, the right side which is b. So the way we like to think about these is these are elements of the tree, we have a child which is another node. It has a right child and it has all these types t1, t2, and t3. So our type and operator in this case is all these different types of operators. So what we have here is we have constraints. Our type system is going to enforce constraints. So it knows when it sees a relational operator what does it know about the types here? From your project. Type t2 and t3 have to be equal. Yes, what else? Type t1 is a Boolean. Type t1 must be a Boolean. Type t3 must be equal. And you can also say maybe they have to be numeric types or like in our language you can just say they have to be the same type. Tech operators work very similarly. They have plus, minus, multiply, divide. So the constraints could be that now here they all have to be the same type. Because when you add two numbers, two n's together, what do you get? An int. Yes. So we're saying that they're all the same and then this constraint says that they have to be a numeric type. So this would restrict us even further by saying maybe it can't be a string. We have our array access operator. On the left side we have the array that's being accessed and on the right side we have the parameter that's inside that array access. So this would be a bracket b. We can see that. Can you tell us about the types T1, T2 and T3? T1 and T2 must be equal. T1 and T2 must be equal? No. Because you have an array of whatever type. So you have an array of ints. What happens when you access an element to that array? What type returns an int? What's the type of an a in that situation? An array of ints. Same thing as an ints? It is not. Very close though. T2 is an array of type T1. Yes. So T2 must be an array of type T1. So we know that A must be an array and we know that whatever that array is composed of must be the same as whatever it returns. So what do we know about T3 here? It's got to be an int. Function applications. So we're going to use this to represent apply. Function applications. And on the left most we're going to have the definition of the function. So it's going to be f. Then we're going to have all of the parameters. So applications. This is not function definitions. This is when we're calling a function. So what does this tell us about the types here? When we call this function. So what do we know about the constraints here? What does this tell us about f? F is the return type. F is? We're going to use this. F is the type of variable foo. The return type is going to be r. So what does this tell us about f? It's a function with types T through T0. T1 through Tk? Yeah. What does it return? It returns r. Yeah. So f. This tells us that f must be some function. It's a function that takes in these arguments and it returns whatever type that that returns. We will stop here. We will finish this on Friday.