 All right, too loud. OK, happy Wednesday? The Wednesday? Yes. So you know the coffee myth, right? Some of you, I heard over here talking about that. So how they decided to eat coffee is these, like, goat burgers, saw goats eating coffee beans and then, like, jumping around like crazy. So they thought, let's do that. And then that's how they started getting coffee. That's the thing. I don't know if it's true or not, but it's true. I don't know if it's true. How do you know that it's true? Or do you know that it's true? Cool. All right, let's get started. So we're going to close out the type section today. So we're going to close out, well, these are this part of types. We're going to go through the structural equivalence algorithm example. And then we're going to do Hindley-Millner type inference. I think we can probably get through today. We can see how that goes. That'll be cool. And then homework six was sent out right before class. And the solution to homework five was also released. And so on Friday, just like before, we'll do practice midterms released on Friday. And they'll go through that in discussion sections to get you all ready for the midterm next week. Cool. Any questions on that stuff? Yeah. Monday. There's only one problem. Homework six is due on Monday. One question. It's just like a fifth question. You'll see when you see it. Let's do a rule by me. Anything else? Cool. All right, let's get to the structural equivalence. Oh, sorry, you guys can't see anything different. It turned into presenter mode, so we hit the wrong buttons. OK, cool. So structural equivalence. What does structural equivalence mean? Yes. What does that mean, though? How are we using this? What was that? How does the equivalence over the type system? So we have a type system, and we want to tell if we have a variable assigned to another variable, is that allowed by our type system? Does the type system actually express and allow this operation? When we see x equals y, we know the type of x and we know the type of y. Now we need to understand, are those types equivalent? If those types are equivalent, then we can allow that operation. Cool. So structural equivalence, unlike name equivalence, which just says everything for two types of the equivalent, they must have the exact same name. That is it. Boom. Internal name equivalence is slightly different. And structural equivalence says, well, are they basically the same object or the same data types underneath? So their names really have no meaning at all. And we're just trying to determine based on the structure of the types. So we talked about cyclical definitions. We want to see if a structure contains a pointer to another structure, and that pointer contains a structure to the first structure. A pointer to the first structure, are those things structurally equivalent? And so to get around that, we're going to start with all entries in our table. So we're going to have a table with all the types on the rows and columns of each. Yes, every type in our program will be along the rows and the columns, as we'll see. And we're going to initialize all entries to true. So you can think of it this way. We're assuming that all types are structurally equivalent. Then we're going to go through each element in this table and say, is it true? Can we try to disprove that? Are these types not structurally equivalent? And just like first and follow sets, whenever we have to look up, well, these two types will be equivalent if type A and B will be equivalent if type C and D are the same. To answer that question, we look it up in the table. We don't do any kind of recursive nonsense. I'm not nonsense. Cool. So we're going to assume that all types are structurally equivalent unless we have proof otherwise. And so the algorithm is simple. End by end table, each entry in that table is defaulted to true when we start. Then we have a while loop. While this table has not changed, check each entry in the table, i, j. If type I and type j are not structurally equivalent by the rules that we set out, then set the entry i, j in the table to false. That's it. Cool. So we can do this. We'll go through, we'll walk through a simple example and your homework is doing more of this, determining structural equivalence and the other types of type equivalence that we talked about. So type T1 is a structure. It's first field, which name is A as an integer. The second field P is a pointer to some structure T2. T2 is a structure. That's first field is an int of main C. It's second field is Q, which is a pointer to T3. Yes. Yes. We do that by looking at the definitions of the types. That allows you to create this table. You actually, because you think about it, what you're talking about now is you're talking about figuring out if two types are equivalent. You don't care if they ever say does structure T1 equal structure T2, right? You don't actually care about any of the variables. You just want to know for later reference which ones are structurally equivalent. So we have T3, where A, the first field is a float. The second field, P, is a pointer to T1. Okay, four. So this is why you need an algorithm, right? Because we can look at this and it's kind of difficult to say just off hand which things are or are not structurally equivalent, right? I mean, it can be kind of straightforward as you get more and more types and becomes more and more difficult. Okay, so in our table, we have three types. So we're gonna have T1, T2, T3, and T1, T2, T3 as the columns and the rows. And we're gonna start with the start of our algorithm, right? We're gonna initialize everything to true. So let's think about the properties of this table. Is the diagonal of this table ever gonna change? Or I guess one of the diagonals. Well, is it, is this one? I don't think it's considered diagonals. We'll go with this diagonal. No? Why not? Everything is going to be structurally equivalent to itself, right? That should never change, right? That's something we can guarantee right now. A type is always structurally equivalent to itself. Now, if I change, let's say, not even looking at this, let's say I change T3 is not structurally equivalent to T1, let's say I change that to false. Could I update the table at all? So if I know that T3 and T1 are not structurally equivalent, does that tell me anything else about this table? Just looking at the table, not even looking at the types. Yeah, so if T3 and T1 are not structurally equivalent, I also know T1 and T3 are not structurally equivalent, right? Cool, so I can use that knowledge actually while I'm making this table and not go through every single element, right? I can go through all of the elements in one upper half or bottom half of this table. So we're first going to look here, T1, T2. T1, T1, we already said these are always going to be structurally equivalent. Cool, is T1 and T2 structurally equivalent? So how do you tell mechanically, you're the computer, how do you tell? Check the types. They're both structures, that's the first thing. If they were not structures, you could immediately say not structurally equivalent. If one's a structurally one's a pointer, you can say immediately not structurally equivalent. They're both structures. So what does the rules tell us? How do you know if two structures are structurally equivalent? Their members are on the same type. So what specifically is important about the members? The same order? The sequence, the order, right? So the first field here is an int. The first field here is an int. Is that structurally equivalent? Yes. That's by our first rule on structurally equivalent, right? Two basic types are structurally equivalent. Sorry. The same basic type is structurally equivalent to itself. Ints are structurally equivalent to ints. Cool. Then we have to look at the other field. So is a pointer to T2 structurally equivalent to a pointer to T3? Yeah, so they're both pointers, right? That's yes. Pointers are equivalent if what they point to is structurally equivalent, right? So what they point to is, so the question is, is T2 and T3 structurally equivalent? Yes. Yes, based on our table, right? Just like First and Follow says, we use the table. We don't then go and try to calculate T2 and T3. We look at the table and we say the table says they are structurally equivalent. Therefore, I say they're structurally equivalent. So therefore, do I think that T1 and T2 are structurally equivalent? Yes. Yes. Cool. And I go into here, T1, T3. So are they at a high level? Do they, are they at a high level structurally equivalent? Yes, they're both structures, right? So then two structures are structurally equivalent if each of their fields. So is an int structurally equivalent to a float? No. No. So we can change that to false. And we're also gonna have to change that other entry in our table to false, right? Do we need to keep checking the second field? No. No, we've already determined it's false. There's no possible way. It doesn't matter what is here, right? As soon as we get that first inconsistency and say they're not structurally equivalent, we're done. Cool. Then we're gonna check T2 and T3. So are T2 and T3 structurally equivalent? No. Well, we check their structures. We check their first field. We see that we have two different basic types. So we say they're not structurally equivalent. When we change to false, update this other part of the table to false. Are we done? So we've gone through all the elements. Are we done? Is this it? No, we did one iteration of the table, right? Now we have to ask, did we make changes to the table? Yes, so we have to do it again. So we say, are T1 and T2 structurally equivalent? Well, we check the first fields, ints, are they ints structurally equivalent? Yes. The second field is pointers to T2 and pointers to T3. A pointer to T2 and a pointer to T3 will be structurally equivalent if T2 and T3 are structurally equivalent. Are they structurally equivalent? No. No, so this could change to false. And I would keep going through this again, but I've actually already said, so can this table change anymore? No. No, so this would be another way you know you've done when no types are structurally equivalent to any other. If not, if this was true, if T1, T2 was true, we would go through it one more time and make sure that nothing changed. If nothing changed, then we would stop it. Well, questions on this and this algorithm? Well, some tips to think about when you're doing this on an exam, when time is limited. If you have a lot of types and you have some types that are pointers, some types that are structures, some types that are, I don't know, definitions of basic types or something, do you need to make a huge nine by nine table? Right, because you know that pointers are never gonna be structurally equivalent to structures. So you can make separate tables just for the pointers and separate tables just for the structures, right? So you can group them that way and think about comparing them that way. You can still do it with the nine by nine, it'll work. It's a little helpful tip. Cool, that's types, you know, everything you know about. We get to super cool type systems. I know it didn't work, let's see if it works. Wow, this is amazing, this is a game changer. Cool, okay. So, we've been studying type systems for the last week, plus, something like that. Like a certain amount of time. And in every single one of these examples, right, the programmer must declare the types of all the variables. Right, and is this true in your day-to-day programming life? Is it true in the projects that you write for this class? Yes. Yes, right, you have to declare that A is an array of zero to five of ints. You have to declare that variable I is an integer. And then this way, so the question is why, right? If we go back to why, why do you have to declare the types? Well, it's because, so that that way, the compiler when it sees A bracket I, what is it checking for here? Yeah, so what is the type system checking here? What is it, if this is a valid expression according to our type system or statement in our type system, what does that mean? What is it checking for? What was that? So, which int, the type of what variable? Yes, so the index into an array must be an integer. Right, that's what it's checking. It's checking to make sure that this variable I is an integer. What else is it checking for? So that's one thing. What's another thing? Yes. Offsets won't apply by the scalar I jump in the size of those offsets. So in order to know the size of the offsets, it has to know the type, the base type of the array. Sure, so it needs to know the base type of the array so that when it computes the Ith index into A, it knows how to actually compute the Ith index based on the size. So here it knows that the array is composed of ints, so the C code when it spits out XA6 assembly knows exactly how many bytes to do. What else, what else is this program, this, what else is happening here? We put it to the array count to be the type integer as well. Right, so how do you add the type into the array? What if we tried to put a string here on the right-hand side? Would the type system allow us to do that? No. No, it shouldn't because it knows that A is an array of integers. Therefore, if you try to, so an array of integers if you apply the array access operator, if you think about it type-wise, it's going to return whatever that type is. So here it's going to be an int, right? So if I'm trying to assign to one of those elements in the array, I can only, the right-hand side better be an integer, right? So you can see even in a very simple case like this, there's complex things that are happening here, right? So part of what we're going to be thinking about, and so the question is, right, so if we know if we declare a string I, and we try to say AI equals one, what's the compiler going to say? And it's going to throw a type error and say, you can't index into an array of a string in a C-like language. And could we do something like this? Change it back to N-I? Do AI equals some string testing? No. And so this, here we can see and we talked about this when we first talked about type systems. You are all very pro-type systems. And I think part of the reason stems from this, right? The compiler is stopping you from shooting yourself in the foot, right? By telling the compiler that, hey, I is an integer and A is an array of integers, you know that, hey, this array access is good. This index is an integer, which is good. And it would say, hey, this is bad though. You can't assign a string into an array of integers, right? This is the type system doing its job to pretend it's like a beautiful straight jacket, right? To stop you from harming yourself. You just hope you can do other things with a straight jacket on. So, do you need to specify exactly the types of all variables in all the programs you write in C++ or in Java? What was that? All variables. Yeah? No. No? Give me an example. C++11, you can use the auto keyword. Yeah, it's cheating. C++11, you can use the auto keyword. Explain to me exactly what it does. No, just figures it out, yes. It's not quite exact enough. But for usage, it's good, yes. For C++, you can use the var keyword. So C++ has the var keyword, which C++ actually has two, if I remember. There's var, which I believe is the same as auto, which will figure it out. There's also another one that's like a dynamic keyword that it actually doesn't figure out statically. It's usually like the returns of link calls, but I cannot remember what it is. If somebody remembers, let me know. I think just look at some similar words as the var, because you can sort it down like you can, you don't need to say it's tight, but you can, but you can also inference at the end of it, as long as... Cool, so then it may be using something like this. So switch is the, what is it? Objective C replacement, basically, on... Is it just iOS? No. It's iOS in there. It's mostly iOS in devices. It's usually the Objective C that you just don't want to stay with the Macs. Right, okay, cool. So, but the idea is you get to think about what about parameterized types, right? Have you done writing classes in Java with generics or templates with C++? So there you're kind of, you're kind of not really specifying the exact type of a variable, right? You're saying that this variable can have some type T and that the person who calls this function will tell you exactly what that type of T is, right? So in some languages, so the idea is now instead of, right, you saying this function only operates on these types, you allow the person calling your method to give you the types, right? So generics, it's called generics in Java or C sharp, templates in C++. You've all seen this, right? No? Okay, there's not a lot of hand raising. Not against my, okay, cool, right? So what does this look like? So in Java, we can make some class called the chooser class, it can have some random thing. And so here it has some generic function, we call it a generic function, right? So it's a function, so the parameter is some type T, right? So in addition to the normal parameters to this method, there's an additional type parameter of T. And so we're saying this function choose, I'm not saying as the programmer who wrote this function, if it takes in integers or strings or whatever, what I'm saying is, as long as you give me two types that are the same, I will return a type of that same type. So if you give me two integers, I will return an int. If you give me two strings, can you call this function by passing in a string and an integer? No. No, because you can only pass in one type parameter, this capital T, right? That's how you know I am specifying and constraint that these are the exact same type. So what does this thing do? Well, it's a very generic method to randomly choose between one of these two, either first or second. So we can see we're getting the next int from our random number generator and saying if it's mod two is zero, so if it's an even number return first, if it's an odd number return second. So what's the benefit of writing this function like this? I don't, if I couldn't parameterize the types, I would have to write this for every single type or I'd have to use some crazy horrible C hack and use void types here. And then I could do that, right? But then I miss this benefit that these two types have to be exactly the same, right? And then it returns the same type. So if I were to, let's say return a void star and taking two void stars, the person who called my function would have to make sure to cast the result, whatever they expect it to be, it would also, the burden would be on them to make sure they passed in two types that were the same type, right? But here now the type system can check this and the type system can say, oh, you're calling this choose function, you're passing in two integers, that means the return type is an integer and it makes sure that both first and second are integers. So you can think about this actually raises the level of abstraction up, right? Now we can talk about not just parameters to functions, we can talk about parameters to functions and types as parameters to functions and let the person who's calling our function tell us the types, it's a very powerful idea. So how does this use? So we can have some class, we can have of course our magic main method and here we can set an int, we can have an int x and a int y and we can print out chooser.choose x or y. But we know which number it's gonna output. No, it could be either of them. We can have string a as foo, string b as bar and then we can output chooser.choose a or b, right? Because the key is the functionality of the choose method just, the choose method doesn't care about what its types are, it's not trying to perform addition on first or second which would mean that they had to be integers, right? It's saying that this function is abstract and generic enough that it can be applied to any types as long as those types are the same, but yes. In Java I think you can force the type to be comparable like make sure that t implements or extends comparable or whatever. Is there an equivalent to that in C++? I don't remember, I don't know. Anybody familiar with C++ enough to know if you can specify that the types have to be subtypes? Yeah, I can't remember exactly the semantics of how you do that in Java. I don't know if you can specify constraints on t and say t has to be some subtype of this or has to implement i comparable or something. Yeah, I'm sure the same thing exists in C++. And if not, then you get around that with subclasses, right, or interfaces. So you take in an i comparable first and an i comparable second, then that's what you use. But that's also tricky because you're not saying that exactly the same type that is also comparable, so. Cool. There's questions about this? You're all familiar on this? Cool, so this is relating the stuff we're gonna do to a language that you're familiar with when I used before. So, we think this is great. We've come to the promise land, right? In some sense. We don't have to specify types in this choose your function, right? But is that true? What did we as the programmer have to say? What choices did we make when we were writing this choose function? Yeah, so a, I had to declare it as generic, right? I had to say this is a generic function. I had to probably, well, if you're me, I have to look at the syntax for how to do generics in Java so I can do the right syntax here, right? You figure it out, you do the brackets, you say type t, right? And then I have to, even though I'm being generic, I have to specifically say the return type is t, the first parameter is of type t and the second parameter is type t. So I have to tell the compiler that these two types must be the same, right? So even though I'm getting some level of abstraction here, I still have to explicitly state the types even in my generic function. And so we're going to look at, so what we're gonna look at is a form, so here we have what's called explicit polymorphism where we had to specifically say that these two types and polymorphism here is an overloaded term, right? Because we're very familiar with object-oriented programming and polymorphism, right? Here we're thinking about it in types, right? That type t, I can call this function with any type t but even though I can do that, I still had to be explicit about where that type t appeared in the function, right? And so this way the compiler can check that when you invoke this function, are you doing it correctly according to the signature? The flip side of this, and I would argue something that gives you even more expressibility is what we're gonna study in that, hey, let's make the compiler do that work, right? If we look at this chooser function, if I didn't specify any types here of what choose returned or took in, could the compiler actually figure out that A, it doesn't matter what type I pass in so that it's a generic function and could it determine that both true, both first and true had to have the same type and then it returned that same type? Yes, that's what we're gonna study, so yes, it's possible. The question is understanding and maybe believing me for a little bit about that. That's actually, in my mind, a cooler way to program them like this, right? Yeah. But when you call the function, don't you have to? Go ahead and do it here? Yeah. I'm asking you, do I have to do it here? That's what I did when I used it. So you have to, in Java, you have to specifically specify what you see, but if here you're calling, if, I think even an older version, you always had to move it, in newer versions, if you can tell based on the types of the parameters here, which exact, what your exact T parameter is, then you don't have to specify. The problem, I think, when you get into, I think the problem you can get into is if you have like two things that are subclasses of the same superclass and it's not clear what exactly you're passing in, right, then you get into weird issues where you have to explicitly specify. But the idea is, man, wouldn't it be really cool if we could get the type safety that we want, right, that we already established we want without actually specifying any of the types in the program, right? The idea is if I knocked off this int here, int here, string string, knocked out these T's here, could the compiler actually figure out, yes, here I'm calling chooser.choose with ints, and here I'm calling chooser.choose with strings and that successfully type checks. Cool. So that is a form of implicit polymorphism. So instead of being explicit about the types, right, and even explicit about the generic types that we're talking about, can we do this implicitly? And so that now you, the programmer, don't have to worry about, well, you don't have to worry about specifying all the types, but you have to worry about your program type checking, right? If the compiler can't type check your program, the program's not valid. So dynamic languages have this property too, right? So any do Python or JavaScript or Ruby or one of these dynamic languages, do you need to specify types in that language? No, you don't even need to declare variables often, they'll just pop into existence, right? But how do you know when you're writing one of those languages whether you made a type error or before you run it or while you run it? While you're running it, because it does not type check everything beforehand, it is doing type checking, right? As it executes, it's saying, ah, you tried to call some weird method on an integer that should be a string, right? So it is doing type checking at runtime, but you don't know beforehand whether your program type checks or not. So with implicit polymorphism and what we're gonna study and look at is the type checker can actually statically attempt to assign the most general type to every single construct in the program. So every function, every parameter, every variable will have a type in this program and it will be the most general type possible while still satisfying the type constraints or you'll get a type checking error. This is actually really cool. And so we're not gonna, I just had an idea. I mean, I'm not gonna do it, but it'll be fun to make you program in a language that did this while you're learning this. Something, so if you want to dig into this more, Haskell is a really good language for this. I believe F-sharp will do this too as will any of the ML family of languages. OCaml is one that's used very heavily. It's actually kind of crazy because I did a project like a static analysis tool in OCaml and I didn't know it at all, but over time you get to the point where you feel like you're fighting with the type system because it's not doing things. And then when the types all fall into place, your program actually works. Like, it's weird. Like there's, I don't know, the type system is so good that when your program type checks, it's likely to be correct whereas now your program type checks and just pray that everything works, right? Cool, so let's dig into this. So, what's the idea here? So we're also gonna use slightly different syntax here that's lifted from kind of the OCaml ML style of languages. So here, I'm defining a function foo which takes in a parameter x and what does it return? So no explicit return statements. Just whatever, you can think that statements kind of return themselves. So I have a function foo x. So what's the type of foo? So let's think about this and discuss this. What is the type, what do you think the type of foo is? What is the most general type that you as a human could assign to foo? Is foo an object? Think about our type constructor rules, right? What is foo? A function, right? So this type should be a function. So then what do functions mean for the types? Parameter types and return type, right? So what is the, okay, so how many parameters is it taking in? So it takes in one and returns something and in this language we'll say we can't return more than one thing, we'll keep just one thing, right? So here we have, it takes in one thing and returns one thing. So what would be the most general type that can do that? So if I had, so we're saying foo, we know it's a function, it takes in something and returns something, right? So what would be the most generic thing that could take in and return? A function, like this, here's a parameter, an integer. What's more general than an integer, can an integer hold a function? A variable, let's just call it any type. Well, let's go with the other example, t, right? Any type t, it could be a function, could be a pointer to a function, could be a pointer to a structure that structure contains whatever, right? Most general would be, just like we saw in the generic example, we'll just represent it as let's say t. So then what could be the most generic type that foo could return? Not that function foo, but in general. Is that? S. S, why is that more general? It's a different type. A different, a completely different type, right? So here we're saying there's absolutely no relation between what it brings in and what it returns. But we had this function foo, right? So what is then the type? So this would be general, so this would be like the most general one parameter function you could write. So now if we have a function foo, this function foo, specific function, we know what's the overall type? Function. We know that foo is a function because it's defined as a function. And so it's a function, how many parameters does it take in? One. One. So then what is the most general constraint, the most general thing we can come up for with this parameter x based on this usage here? T. T. And what about the return type? T. It has to be T, right? Is this more or less general than this function? Less general. Less general, right? We added a constraint that says the output type is the same as the input type. And then so if I had another function bar, this is more or less general than foo. Yes, it's more specific, right? You can't call bar by passing in a structure, right? You can only call it by passing in an integer. Okay, so in our old syntax that we used in the last section, we would say function of T returns T. What we're gonna be using to be more consistent with the traditional style of function types is we're gonna use this style which I was using there, right? So here this means a function, so this whole thing means a function, right? So this means it's a function that takes in one parameter, that first type of that parameter is T and it returns something of type T, cool? Just a little syntax variation, but this will help you, if you do decide to go and pick it up and try to learn some of these languages, you won't like freak out when you see the output of the type because it looks exactly like this, cool. So now I can have two functions, right? A function foo, which is the same. A function bar which takes in a y and returns foo of y. So what would be the most general types I could assign to foo and to bar? So what about foo? A function which takes in T and returns T? What about bar? Okay, so foo is the same, right? Foo didn't change, it makes sense, the type doesn't change. So what does the fact that we're using y which is the parameter to bar as the argument to foo, does that constrain the possible types of y, does it? Does this mean that y has to be an integer? Does it mean that it has to be a structure or a pointer? What do you mean the same type as x? So what's happening with the return of foo y? Just like with x, it's gonna be returned from bar, right? So from the type system, if we know the type of foo, right, we say we pass in some type T and then we get back the same type T. So then what would bar, bar's type look like? Similar thing, right? A function that takes in a T and returns a T. Now the important question is, are these the same Ts? Are your guesses and noes? We get a T out of it. Do you say that the odds seem like, you know they're all Ts, same Ts? Yes. Isn't it that because you've used the same template, identifier as soon as the user invokes the function foo and passes in the parameter, from that time on, in the program execution or in that scope, all of the Ts that are in that context are all gonna be of the type of whatever it was that the user threw in to the function foo originally and if you had used some other template identifier, then it would be the other way. But in this case, Ts, T, so once T gets set. So let's think about it like this, it's a good question. What if, so what if later on in my code I put bar 10 and then foo hello? Could I do this? Are the Ts the same? Does this even make sense to talk about the Ts being the same? When do we care about that? So there's two things here, right? We have foo and we have bar, right? So what we're saying here is we want to invoke bar and now this type T will be an integer. When we invoke it and then when foo gets invoked, it knows that it's gonna invoke integer of bar, right? The parameter, type parameter that's passing a foo is going to be integer. But here with foo, now I can invoke foo with a string and this time it's invoking it as a string so it's returning a string, right? So it's important to think that the T only matters inside its own function definition. This is a completely valid alternative type for bar because when we're looking at the type of the single function we only care about parameters that it gets passed in and the return type. Whatever it calls helps us figure out what the return type is, right? The fact that bar calls foo means that the return type of bar must be the same as the input parameter. That's the constraint that it adds. We don't care that this is called, we call this T or W or Y or Z. All we care is this is a function that takes in some type T and whatever type it takes in is the same type that it returns. Sweet. So now we can do cool things. We can write functions like anybody ever write a function like this which is the biggest number, maximum, right? So give me two numbers or two anythings. If X is less than Y then return Y otherwise return X, right? Remember no explicit return statements here. It's going to be the statement here is going to be a return. Ideally they would be of the same comparable type so you're not comparing it to the string. Yes, so we'll see that in a second. Cool. So what would be the type of max? So what is the overall type of max? A function, definitely a function. How many parameters is it taking? Two. And it returns one type of both. I mean that's, you got the basic structure down there. So from looking at this, what do we know is true? What does it depend on? Going back to that question. When trying to figure out what's the most general type of X and Y what does that depend on? So let's think about just without even thinking about this comparison operator here can X and Y be different types? Yes? The base level though needs to be the most generic one with the same type. So could it be instant strings? Yes. It can be only when it comes to the length of the string in comparison with integers. Cool. That's why we're ignoring the comparison here. So if you say that X and Y can be different types so you can have an integer string then what is the return type of the function max? Integer or string. What kind of type is integer or string? The type of class. Right. So actually now we're exploring different types of type systems that actually kind of can allow that. I did not think we'd get there. So let's think about this. So assuming we don't have a type to represent we can't describe ORs in our type system, right? You cannot define a variable X as either an int or a string. Which is kind of tricky, right? How would you say that a variable X can be either an int or a string? Right? So we can't have that. Then can we have X and Y be either an integer or a string? Why or why not? Yeah. I just had an idea. We could favor one. So we just assume that whatever they're trying to compare it's a type X and then when we do the comparison we can ask Y as the type of X and then return. But then not the comparison though. Not the comparison. Well we'd still, I guess, if the comparison operator is defined but either way we'll just, if we're going to return Y before we return it we'll cast it as type. So why would you have to cast it though? I have no idea. So when I call the function I know I'm going to get back. Yes. So when you call the function you know what type you're going to get back, right? You don't want to call a function and say ah, it's going to return either an integer or a string. I hope you're prepared for both. I guess you could make a type system do that and you get into really weird stuff. So there's two things here. A, X and Y need to be comparable, right? They need to be, it depends specifically on our less than operator, how that's defined. Let's say the less than operator will work on any basic types. So they just have to be the same, right? They have to be the same basic type. We can't compare two different types that aren't the same. So because of that comparison we know X and Y have to be the same type and because of this branch in one branch of the if statement we return Y in the other branch we return X those better be the same types, right? Because otherwise we have a program that now we don't know the type that it's going to return it depends on the runtime path which would be a horrible way to program, right? So this would be if it just returns Y I'm not going to worry about that. Cool. Okay. So, ah, we're out of time. Alright. We're going to finish this up on Friday. I was like, you're lying.