 All right, happy Monday. Let's get started. Any questions before we keep going with our tight systems? And as soon as I move to go, something's already in my hand. I think we may be able to finish this up today. It would be awesome. Okay, so we've been talking about whose defendants. Talking about tight systems generally and more specifically, we've been talking about what are the different components of a tight system and what makes up a tight system. So we've seen different ways to actually create new types. So we're defining this very simple tight system that has pointer structs, arrays, with ranges and functions. Very simple. So now the question is, how do we actually use tight constructors? So we briefly touched on a little bit. Somebody reminded us why do we want to declare new types? Yeah, so we may want to create and basically model in our program in some way some new type of object. For instance, arrays. We want to have arrays because we want to have items in a list and so the tight system allows us to define that. We also might want to have pointers to things. We also might want to use structures so we can group things together. All this kind of stuff. So in our language, we're going to declare types similar to this. So this is declaring a new type 7 meter, or just cm, which is defined as an integer. Are we good with that? New name on the left, the type that is defined, that this type is defined as is on the right. We can also define things like RGBA. So what does RGBA stand for? Red, green, blue and Adam? Yeah, opacity, transparency. So that would be another number that can tell you how much you can see through that pixel. So this is one way to represent a color channel. We're saying it's an array from zero to four. Is that what we want to do it? Out of the integers. So we're saying the type of the array, so we're saying it can be technically five elements, which will change real quick. Look at that. Just in time, compilation is lost. So four, we have zero, one, two, three elements here in our array. And they'll each stand for, in this definition, red, green, blue and alpha. So then we had an array of 256 by 256 of RGBAs. There we now have an array of arrays, which could define a 256 by 256 pixel picture. But we could do that just by using the arrays. So remember, one of the important things is, we could do whatever we want to do just by using this array zero to three of ints. But giving a name to what this is gives our program more semantic meaning, and it helps us, later, and other people who follow us when they try to maintain this code, to say, oh, I know what this means. I know what RGBA means. This isn't just some array of random array of four elements. This is a very specific thing that the programmer wanted. It helps the type system to catch errors. So if we ever try to pass an RGBA somewhere or it doesn't belong, the type system will be able to tell us. Cool. And yeah. So there we go. We can make a PMG. This would be just a 256. So you need some extra information to say what's the width, what's the height of the object, all of that. So important things are, I'm building these complex types. I'm using type instructors by using references to other defined types. So here I'm building an array out of a defined type that is not a basic type. This type is defined here. Do all these new types? So specifically when I make a type declaration like this, what am I doing? I'm giving this specific type on the right. I'm giving it a name. That is what I'm doing. I'm saying from here on out, whenever you see centimeter, think integer. I see exactly what that means like I'm being kind of vague. But we're essentially defining this name. Now the question is, if I were to cover up with something that doesn't exist, I don't know if I could do something in front of it. Right? Does this type on the right, so is the thing on the right here, array 0 through 3 of int, is that type? I'm declaring a new type called RGBA, which has this type. Now the question is, what is the name of that type? So let's think of the first one. Is integer a type? It should be consistent. Is int a type? Yes. Now it is yes. Does it have a name? Int. Right? That's what it's saying. Does array 0 through 3 of int have a name? What was that? The int bracket. The int bracket? Group of integers. Just being able to be a type, as int is just a name out of all the parameters for what you do. Right, so we've constructed a new type, right? We've used our type instructor rules and said we're going to create a new type. This type is an array of 0 through 3 composed of integers. And here we're specifically saying this type declaration, we're giving that type a name. We're saying RGBA is this array 0 through 3 of int. But unlike int here, and here we're even then using that name. Right? We're using this RGBA name inside this type declaration. The question is, does this have a name before we've given it the name of RGBA? No. No, it doesn't, right? We're using our type constructors to create some new name. Right? Just like... Well, I'm trying to think of this as a valid. Just like you can... Okay, it's not good enough. So this brings us to anonymous types. So in our language, and this actually often happens, this can happen in C as we'll see in a second, we can declare variables. So here I'm declaring a variable x, just like the syntax we used to, type variable. Here the type of x is an array 0 through 4 of int. So does an x have a type? You're in the type system. Does an x have a type? Yeah. Int is your array 0 through 4. Does that type have a name? No. No. So what's the difference between... Hey, there we go. Here we're declaring a new variable y and saying this type is RGBA. So what's the underlying type of RGBA? An array 0 through 3 of ints. But the key thing is, and we'll see why this is important later, but we're going to see, when we try to say, can we set x equal to y, or y equal to x? One of the factors that's going to come into play is the name. And so the important factor here is, this is an anonymous type. There is no type here. You can think that... Sorry, there is no name of this type. x has a type. It's very clearly defined here, but there's no name for that type. Here with y, y has a type and has a name. So this is something that I, I always did not realize until I taught this class. So when you're defining a struct in C, you have to add a semicolon after it, right? Isn't that super weird syntax-wise? Do you have to use a semicolon after you define a function? No, it seems got crazy, right? We're trying to define some new type why we need a semicolon, especially when you're arting up the braces, so it knows when the thing ends. It turns out the reason is because you can define variables that have that type. So you can actually define a list of variables, x, y, z, whatever you want, separated by commas, called by a semicolon there. And so that's why you have to include a semicolon after it's dropped. So here I'm declaring a... Ah, I have a block here. Man, this is what happens when you just make... This is a problem when you make local changes, then they have local consequences, right? So we're defining a structure that has two fields, a and b, where a is an int and b is a character, right? Does that structure have a name? No, but I'm saying that there is a variable z that has that type. Why did this be useful? Yeah, it could be that this structure is only used in one specific place, right? There's not really a good semantic name for it, and so you can have an anonymous structure like this. Cool, but the key part, especially when we get into things in two minutes, no name. It has no name. I keep saying this, it's gonna take a while. No name. Has a type, has no name. What's the name if I said it? It has a name, you can say it has no name. Okay, cool. Okay, so now the question comes to type compatibility, right? So we talked about type systems. We talked about we need basic types, we need type constructors, we need type compatibility, but that's, so the question, what type compatibility tries to answer is which assignments are allowed by the type system, right? And so here's where I want us to talk about this a little bit. So think about this. If you have an assignment a equals b, right, and you saw in semantics, we can tell you exactly what happens when we see a equals b. Right? Now the question is, does that make sense from the type systems perspective? So let's say a is an int and b is a float. Would you want to allow this in your type system? You're the designer. Why are you why not? Competing factions. Who says yes, they would like to allow this? Who says no, they would not like to allow this? Who is launching a protest vote? It wouldn't be a protest if you end up voting. Okay. Cool, so why? So some of the yeses, why would you want to allow this? You should call to truncate around the float when you want it to be coming in. Ooh, so what does truncate mean? How the guy grows it. Why do you need to truncate? Right, so remember we talked about types and we talked about for each type it's a set of values that it can hold, right? So it can encode all the type, all the values that float can represent. No, right, because the float has the decimal component. So typically what happens is we just if we see this and we're to allow this we want to cut it off. Why might we not want to do that? Why would you maybe not want to do that? Who took the no side of this? Precision? Precision, what about precision? Yeah, right, what if it's 1.99 and you truncate to 1, right? Fundamentally, right, you as the programmer if you're saying, hey I have a float copy the value inside that float and copy it into an int, right? A, these types are not the same types, right? Ints and floats are not the same types. So maybe an argument for disallowing it is saying, hey, maybe the programmer is making a mistake here, right? And I want to help them out. Maybe I want to, then they need to make the decision do I round the float to get an integer, do I just truncate it? The yes crowd is maybe, I don't know has more faith in programmers perhaps and says no, no, the programmer definitely knows in this huge specification document on C that when they assign a float to an integer it's going to truncate it and they definitely meant to do that because that's what the lego says, right? But if you can prevent people from crashing the rocket so don't you think you should use that power to do so? Man? Throw a warning, so I think we would at least throw a warning, right? So that's kind of also your decisions as a lego designer, right? Do you want it to be a warning? Do you want it to be an error? It's frozen down. I don't have any, there's not any set in stone right answers so things to think about, right? But different legoes do this differently. What if A is a float and B is an int? Do you want to allow this or disallow this? Why? Do you feel like the world's worst judge in this case? Past judgment on this? Why would you allow it? We're not actually losing any information although is that not true? I think it is. If B is 2 to the 32 will it? 2 to the 31 minus 1 Or if it's unsigned? Yeah, if it's unsigned, that could maybe cause some problems. But here we know it's an int so it's not unsigned an int. Cool. So this is what type compatibility tries to answer and we're going to look at three different strategies of ways you can design a type system that has different strategies for this. This is just one simple example we're going to do a little bit more complex things. The fourth thing we talked about was type inference. The question is what are the types of expressions and other constructs? What's the result of that? So if you have an int plus a float A, do you allow that operation? And B, what type does that return? In most languages it returns float in some languages it's not alive and you can't do it. And so in C so if we have A plus B A is an int B is a float it's going to return a float in C it's an error in ML based languages like specifically O'Cammels and I remember this in I think really annoying is your programming it's like you have two numbers you want to add them together you don't want to be reminded that actually you have to change this int into a float and then use I believe it may be a different operator I don't know it's been too long but let's say it's the same operator it's going to be easy you still have to cast things so A times B if A is a string and B is an integer would you want to allow this? Yeah, I think I've talked about this before but in C this is a clear error you can't multiply a character string times an int it doesn't make sense but in Python of course it definitely makes sense and it returns A repeated B number of times the other thing I was thinking of why did this come up so often that I guess C in one verse I created this thought that this would be something really useful to have I mean I don't it's one of those weird things that once you know it's there you kind of find ways to use it but you never are like in other languages like in C or Java you're not like no why can't I multiply this string by this integer this language is so cumbersome yeah I guess if you're doing ASCII pictures or something but even then you just write one function to do that it's like the easiest function like it doesn't have to be in the language itself especially as an operator okay so now we're talking about the four different aspects of the type system we really need to dig in and talk about okay what are the different rules that people try to use for type compatibility and this is principally about type equivalence so how can we determine if two types are equal right if we see A equals B when do we allow that and when do we disallow that right and we want this to be consistent we don't want it to sometimes we want a standard rule and a standard way of thinking about this and determining this so in our language if we have a type declaration of centimeter as an integer we have an inch as an integer we have a variable x that's the third of the centimeter a variable y that's the third of the inch the question really comes down to and this is something we alluded to earlier do we want to allow x equals y and what would you use to tell the difference overload which operator the assignment so overload operator to then do what actually so there is if you want to look at a language that actually does that there's a language Scala which is like a functional programming language that runs on the JVM and they actually do that so they will look and see if there's any functions that will translate let's see in this case it would be an inch to a centimeter and will automatically call that function to make the type system work they also do a lot of really cool type systems stuff in there but so let's say we don't have that powerful of a type system no, why not it's just because of a sign it doesn't have the same meaning right it kind of it partly depends on your perspective is our second year's an inch is the same thing yes so this is kind of where why it gets tricky right the programmer has specifically said I have centimeters here a new type of centimeters and a new type of inches right and so why would I want to allow an inch just to be somehow converted to a centimeter right that doesn't make semantic sense on the other hand we look at it more of the focus kind of maybe engineering type we look at it and say well they're both integers they're both going to be the same amount of storage I can surely copy one to the other right and that's not going to do anything so this really comes down to the question of what do we do here and so it depends on we're going to talk specifically about three different types of type compatibility rules so the first one is name equivalence so this is you can think of the most strict form of type compatibility so just from the name alone how would you think what do you think this goes by yes the same name associated with the type so we'll be able to assign centimeters to inches no right we'd have to pass it into some function that would take in centimeters and give us inches which in that case makes sense right that is really what we want to do and then we can leverage the power of the type system to really help us do that so types must have the exact same name to be equivalent right we'd say error can't do that so let's think about this different case we have I just noticed this is the wrong way not consistent with our earlier examples there we go cool so I'm declaring two arrays 0 through 4 of a's and b's so can I set b with name of equivalent yes they do not have names how could their names be equal if they don't have names the lack of a name is not the same thing as having the same name if you met someone and you both I don't know I guess maybe that doesn't make sense we both like don't have first names you're like hey we both have the same first name no you just don't have first names those of you not allowed in the name of equivalent trip people up is why I hard so much on the fact that anonymous types do not have names does this make sense dude is this what you would want or expect from a program language yes no somebody's yeah it makes sense but in something like this so how is this case different from the centimeter and the inches case is it the exact same situation because I asked the question and it's taking too long it's the same situation because obviously they're different types but no it's a different situation because there's no fundamental difference between incisors between centimeter inches the data types less than abstraction lower levels so it's the same same concept and we can apply that to anything if you want to set an integer equal to a string you just overload the operator to do the casting it's just data it's just 1's and 0's anything can be copied to anything else it shouldn't be the problem of the compiler designer it's going to be the easiest way to build a compiler nobody will use it that is the downside yes you want to use something that people will use and will stop them from shooting themselves in the foot but will not restrict them so much they can't write programs in your language right so it's a fine line if you think about all the possible programming languages that have been invented since computers came about right there's been a probably a large exploration of this design space so yeah okay so the question is would you want to allow a equals b from the information that we have here in some sense we have an array of 0 through 4 of integers and here we have an array of 0 through 4 of integers these kind of sound like the same thing because we have no additional knowledge part of the problem is the programmer did not give them a name so we can't say that yes they're the same thing or no they're not the same thing so you can think about name equivalence is very pessimistic and thanks to your very silly programmer things have to be and if you think about it this is also easier to code from the compiler's perspective you just check names that's it doing type system type system done keep track of the names of types the assignment operator is not out what about this case let's swap it around again so here I'm declaring two variables a and b that are integers arrays is 0 through 4 of a so now can I set a equals b so first we're going to think about before answering this question what's different from this than the previous one it's the exact same character on the screen so a and b are declared on the same line right which means they definitely have the same type but will name equivalence allow this assignment why not they don't have the same name because they don't have a name yes perfect right specifically not name and now this is when this starts to break down this really doesn't make as much sense this is almost I would think too conservative and pessimistic because here you are declaring two variables of the exact same type why would you not be able to assign one of them to the other just because they don't happen to have a name I guess it's theoretically possible to do this and then use a and b as if they were two completely different types but it'd be very bad practice but if I define type a as an array 0 through 4 of 8 and I say a is an a and b is an a then can I say a equals b yes because they both have a name that name is an a look at this easy right, painless then we get to an internal name equivalent so here this takes name equivalent one little step further and does a slight tweak to it to fix what is probably the most broken thing here so the idea is now we're thinking about this from the compiler's perspective and really this is the downside I see internal name equivalence is now you have to think when you're programming what's the compiler going to do here so the idea is the compiler has to give different names internally to the different types whenever it sees an anonymous type it can't just treat it as nothing it has to give it some name even if it's some arbitrary random name that it never uses again that make sense it has to do that it has to have some later referred to it and so what we'll use is we'll use this fact if the program interpreter gives the same internal name to two different variables then they share the same type so you can think that this is exactly like name equivalence right because every type of the different external name is going to have the same internal name otherwise it's not going to work the type system is not going to work where this comes into effect is in situations like this right where I'm declaring two variables A and B to be arrays 0 through 4 of integers now because these are happening on the same line we know that the compiler will give them the same internal name so we'd say can we do we'd say C is array 0 through 4 so we'd say is A equal to B under internal name equivalence yes because they have the same internal name what about A equals C no why the internal name is going to be different right there's a new anonymous type it has no relation to the previous anonymous type except for the fact that it is the same underlying structure right but name wise it's a brand new anonymous type that we've never seen before so internally the compiler is going to assign the same internal name to A and B and a different one to C questions the same as name equivalence except for this one difference which only comes up in anonymous types then we go to the other end of the spectrum which says well I'll allow it if I can try to figure out that both of the types on the bottom have the same structure I will allow that right so if you have two types that are ultimately integers you'll be able to assign them to each other and all allow it but the problem is then when we come what does it mean for two structures to be equivalence they can hold the same type and what is that it and how do you make sure it's the right ones and zeroes so what do you want to do to have a structure to say that these two structures are structurally equivalence what if I change this are they the same structure they're floats right I mean sorry one is an int and one is a float so then what if I change this then do an int what if I change it like this are they still structurally the same they do not have the same underlying structure cool now this is the tricky one so let me make an argument for yes let me make an argument for no choose one I don't know it would be declared in two different ways so what do you mean declared in two different ways would you have to declare it in order in some cases I don't know no because the way that is organizing that strain you're using your knowledge because you know in C the way this is done is the first four bytes of the structure will be an int and the next will be a strain which is the type that does not exist in C and indeed the first the first byte will be the strain and the last will be four bytes of an integer therefore it doesn't make sense they don't have the same structure because we'll get them bytes on the page let's change it around a little bit what if I change it like this I still know from that argument what about yes they still have the same space that would be some part of and you can put it in any way you want they still have the same space and we specifically know that they have even the same field names here right so then I know a food here should maybe be the same thing as a food here so if there were the other order and they were both string in the strings was a lot longer arbitrarily longer than the other string would it still cast because the size of the string or the size of a character array is based on how many characters there are in it before you get to the null terminator let's think that now these are kind of like C++ classes of strings so it's just some object that we don't really care about the size it's the string class itself will handle the allocation of size and everything so it's not going to be inside the structure itself or do you think it's like a pointer to a string that would probably simplify that a bit more right so what argument would be yes what about let's think through how those arguments go like this okay so here clearly yes these are the same structures now what happens if I do this I'm running out of range of versions are these the same structure they put on some something to give me an argument for yes and no a yes argument they have the same types and what about those types in the same order so we know the structure is going to be the same what would a no be class signature the members of the class are different so you have two objects with different members right I've given these things explicit names right in the structure B I have some X and some Y that's Z well when I put let's use this one when I put set B's when I set Z's cool to be something does that mean the same thing as A's BAS so that's actually one of the key questions we have to ask is what matters does it matter the order and the types of the types of the structure or does the field the names of the structures and those types matter so you could do it I'll tell you you could do it either as well when I say you I mean if you are doing structural equivalence and you are teaching this class you can define it however whichever way you want in this class as we'll see where it specifically go in order of the types as they appear and ignore the names of the fields yeah so like if I had something like this something like something like that so in C and D B so one way to know is you know the size of each of these structures of the things inside the structure so you could say the 0th byte so for struct C would be 8 bytes the 0th byte to the 4th byte would be A and the 4th byte to the 8th byte would be 0 through 3 and 4 through 8 whatever that would be a B in this case under what we're going with yes these would be structurally equivalent does that work? yes does it depend? whether it's doing that or memory copy or whether it's seeing for the type that you have seen it could impact we're going to do these great copy semantics we're just going to copy the value here over here cool but we can't just define structural equivalence for structures we've defined five different we've defined a bunch of different ways to construct new types so we need to be able to tell when those types are compatible built-in types are structurally equivalent only to themselves makes sense remember this is more restrictive than maybe a normal programming language here we're not going to allow any conversions between ints to flows or anything like that strictly built-in types built-in types are the same as built-in types cool pointers to structurally equivalent types are the same what does this mean? do we have an inch of the same thing as a pointer to a centimeter? yes if centimeters and inches are both defined as integers exactly so we have a type centimeter inch we can allow x equals y oh yeah we have int pointer a float pointer b and we say 10a equals b no because they're pointers to different types because int so we see pointer to int and we see pointer to float and we say are these two structurally equivalent well they are if what they point to are structurally equivalent and we say are is int structurally equivalent to float no so here we have a recursive definition we're saying two pointers are structurally equivalent if what they point to is structurally equivalent cool structures so in terms of structure structure equivalent so we have some structure s1 or we have fields x1 through xk which I is that right? types w1 through wk yes okay we have structure 2 which has field names y1, y2 all the way through yk which each of those fields have types q1, q2 all the way through qk so just like we just talked about looking at structures the way we're going to do structurally equivalent is to structurally equivalent if and only if each of the fields in order that they're declared are structurally equivalent right so in this example it's going to be is w1 structurally equivalent to q1 is w2 structurally equivalent to q2 and so on all the way through wk equals qk what does this say about the size of the fields in each of the structures how does it say that so the size of w1 should be equal to q1 yes because w1 should be structurally equivalent to q1 what about the number of fields in sd1 and sd2 how is that stated here the k exactly so the point is this k the subscript is the same in both this is the key that says they have to have the same number and of each of those numbers each of those types in order has to be structurally equivalent as well so once again this is a recursive definition because one of these w1 or q1 could be a structure and so how do we determine if those structures are structurally equivalent we apply this rule again so we have structure a which has a field lowercase a that's an int and field b which is a float we can have a structure b which has field b which is an int and a which is a float so if I have a if I have food which is the type a and b are and bar is the type b can I set food equal to bar based on name of good ones internal name of good ones why why in both cases they both have this different name right we've named this type a and we've named this type b food has a name food's type has a name a and bar's type has a name b they're not the same because they're not the same they have different internal names structure a has a as an int to b as a float and b has b as a float and a as an int can we set a equal to b no specifically because the order is not the same right we have int so we say okay the k's they have the same number of fields but is int structurally equivalent to float no arrays so two arrays are going to be structurally equivalent if what do you think and same type what do we want to be true about their types the types of the elements in the arrays yes they should be structurally equivalent as well right so the range is the same number of dimensions same number of entries in each dimension which is a fancy way of saying the same and t1 and t2 are structurally equivalent how to function structural equivalents we have two functions t1 and t2 so what should be true about their number of parameters it should be the same right that's okay so what else should be the same what was that v1 t2 should be structurally equivalent v2, all the way to tk should be structurally equivalent to vk what about the return types they also need to be structurally equivalent t needs to be structurally equivalent to v why don't i care about structured equivalents of functions what was that Function pointers, yeah so if I pass a function as a parameter of a function how do I know that it has the same type that that function is expected, right? One way to think about this, could I use that function in place of that parameter or that other function? So for all i from 1 to k, ti is structurally equivalent to vi and t is structurally equivalent to v, perfect. Okay, cool, so I can go over this so you can get some homework Okay, so the goal is to determine for every pair of types in the program if those types are structurally equivalent, right? And it seems pretty simple at first glance because we have five rules and we can easily just keep applying these rules in order to, until we get to the base case in which case we say, but the problem is how to handle the following case where you have type t1 is a structure, field a is an int, field p is a pointer to type t2. Type t2 is a structure with a field a and p of a pointer to t1. So what's going to happen when we try to compare these two structures? We're going to say okay these structures are structurally equivalent, if and only if. Int is structurally equivalent to int, is that true? Yes. The second case is if pointer to t2 is structurally equivalent to pointer to t1. How do we solve that? Yes, pointers are only equivalent if what they point to is structurally equivalent. So these would be equivalent if t2 is structurally equivalent to t1. How do we say ifXT2 is structurally to t1. If int is directly going to int and a pointer t2 is pointer t1. So should we loop forever? What should we do? Are these the same? No? So the way we get around this is we're going to first, just like we did similarly to how we did in first and follow sets where we started with empty first and follow sets that we could use, in this case we're going to assume that all types are structurally equivalent and we're going to go through and disprove that fact. So here if we start out by assuming t1 and t2 are structurally equivalent, then when we get to, is a pointer to t2 structurally equivalent to a pointer to t1? Yes, they are structurally equivalent. So this is the basic idea. We're going to create an n by n table where n is the number of types in the program. Each entry is either true or false. Talked about this, the algorithm is actually pretty simple. We just set each entry to be true. While the table is not changed, we go through and check those types. And if they're not structurally equivalent, then we change it to false. If we've gone through all the pairs in our table and we haven't changed anything, just like first and follow sets, that means we've got no new information. So we're done and we've decided the structurally equivalent of every type in the program. So we go through, I guess I'll do this on Wednesday. But there's an example in here. It's very easy. We'll go through it in class together in a little bit. So.