 So I'm going to talk about a fundamentally different way of doing type tracking for Haskell-like languages. Of course, I'm going to be more precise than that in what I mean by that. So the object of our study initially is going to be the variable ending in their type system, which I guess everyone who uses Haskell knows inside out, but I think of course you already had some thoughts from people about implementing a type trigger for the ending in their type system, but just to restrict our attention to the manageable small set of a small language. So let's first think about the minimal for where it makes sense to talk about the ending in their type system. So supposedly a language where we have variables introduced by random fractions, we have application, of course, and we have left findings. And of course the whole point is that the left findings are polymorphic in the bottom, right? Otherwise the left would be super close and we would just have some new magazine type random characters. So yeah, each definition, when you keep it simple, is just a variable and it deserves that it is equal to. So when you think about it, this captures all the, you know, the essence of Haskell or OCaml or Standard OCaml or any of the things in our base. Now in the examples that I'm going to use, I'm going to use a slide for more features. I'm going to use data constructors and I'm going to use pattern matching on those data constructors. So you know, this is your usual Haskell syntax for, you know, case something off and each alternative maps a pattern to a term and patterns are data constructor patterns, variable patterns, and wildfire patterns. But the formal parts of the talk will not include the type derivation rules for these additions, for these constructors and case analysis. First the implementation that I'm going to show is that one, if you really want the details, of course you can just check the implementation or you can check the, I get to that later. Okay, so this is the link and we want to have a time system for it. So the time is that we are going to use, we will have type variables, we will have function types. The type variables are going to be bound with universal quantifiers and the outside layers. So yeah, we only have rank one types, of course, because it has the whole point of things in the standard slide system, but it only has rank one types and that's what makes typing parameters decidable. So, and for the practical examples, again, we will extend it with type constructors, which can have parameters, of course we don't have indices, we don't have fire, kind of type, none of these fancy stuff, but as we will see this is already not to demonstrate the point that I will be trying to make. So, okay, so if we have this language and we want to assign these kinds of types to them, we need a type system and this is where the, this is the new type system. So of course, again, as the programmers hope this inside out, because that's what we use they are, you know, you have variables, the type variables are dragged in some context, the types are polymorphic, so essentially then the different use sites can get different calculations of the same polytype. Application is standard, lambda is monomorphic and standard, let is, so here just to keep again with Haskell, let is also recursive, which you can see from the fact that we type check the definition of X with X being installed already, right. Again, this is a, this is the standard technique to do it is, but it's not necessary, so you can have a separate fixed point combinator separate from, from less, but to keep it close to Haskell, I would be using it and that's it, so it's simple, right, we're done and this is what G seems, if you sweep away, of course, so you need to sweep away a lot of fancy features that Haskell has, so this doesn't have bypasses, this doesn't have, I don't know, five families, this doesn't have GEPs, this doesn't have extensions, a lot of GEPs, but you know, on the ephemeral side, you have punkers and you have, well, I guess the type of references is slightly special, but yeah, but the common port, like the greatest common denominator of all the GEPs is just this system and you get these rules and okay, done, but it turns out that if you want to keep on this, turns out that there's a thing of a subtlety here, which is that this is not syntax driven, right, this is completely non-deterministic and what do I mean by that, okay, so we have a polythene for a variable in a context, so we instantiate it, but how, like why do we keep that particular instantiation out of the set of all possible instantiations of our polythene, right, of course, you pick it such that the whole derivation for your given term will work, right, but that requires either an oracle, which tells you the instantiation or you need to be smarter than that, again, similar in lab there, you introduce a new variable and you have some more type for it, but why that type, why that type, there is nothing in this rule which tells you locally what P1 should be and similarly in lab, right, this is a typo, this should be P0, so if you make this work, we introduce x at the time that its definition has, but how do we know what P0 would be such that we can derive this, so this is the time system, but it's not a time inference algorithm, to turn it into an algorithm you need to turn things a bit inside out and that's where, and that's where the various inference algorithms come into play, now I'm not going to go into the details of this, I'm not going to go into the details of this, but there are two well known algorithms W and M, they are basically the same thing, the basic idea between both is that when you, you don't immediately assign types to terms, you assign a type schema which have some meta type variables inside and then later on you either collect a substitution, so this would be something that bubbles up or you push down a, sorry no, okay so the substitution bubbles up and either you get a type of some sub term and a substitution which you then need to apply to everything else, or you prescribe a type and you get back a substitution such that if you apply that to everything you could be in the type of course, then you will get the type of this sub term and also everything else, and this is the crux of the issue that I want to talk about which is these are not compositional algorithms in the sense that it's not true that you can take, you can look at the sub expression and meaningfully talk about the type of that sub expression in such a way that the type of the expression that contains it will only depend on the type of the sub expression, right, there's, there's a, it's linear because there's a, there's an ordering in, in how you need to, to, and recursion to your sub terms because the, you need to, to process one sub term and you need to use that result to process the next sub term, so that's an example, suppose that I have an application, right, so this is a function application on E on if I just look at W, and I give it a big M as well, it really doesn't matter, so I, I don't know why this stopped working, right, so suppose you have a function application, if you look at the rules, what it says is that you are given, come on, it's not even white, so if you look at the rules, suppose that the first one says that, you know, we get some substitution, we get some substitution which we then pass to the type inference of the, of the first sub term, and then we get back a, oh sorry, then we get, sorry, sorry, we pass in, of course, we pass in the context to, to inferring the type of the first sub term, and we get back a substitution, then we, when we do the inference on the second sub term, instead of passing the original environment, we pass in the environment with the substitution already applied, we have to do this because this is the only way that these two can communicate, right, and as we will see in the next slide, there is to make up an example where they need to communicate in the sense that you, you have something inside this term, and you have something inside this term, and each of them are on their own well tied, but they will not agree with each other, and the full application is not well tied, so of course you can do this the other way around, right, you can flip the whole thing, I can start from the original environment, pass it to traversing F, I get back a substitution, I apply that, and I pass that B, both of these are equally valid, but both of them like break a kind of symmetry, so here's an example which shows that why this is a problem, so let's look at this example, right, suppose I have is just in scope with type maybe a to b, and I have not in scope with type b to b, and I say that full of x is a pair, a pair constructor applied on is just x and not x, so what is the type of full, or what do I get if I try to type check full, any guesses, like what do you guys think the type of full is or should be, right, so what is not well tied here, which has, so okay, so suppose that you pass this program to ghc, so ghc will tell you that bool and maybe a doesn't agree in the first argument of not, so it says that the first argument of not, here, this x has type maybe a, but it should have type bool, right, which sounds reasonable until you think about it, and you realize that, like why, why this, it's not true that, you know, not the x on its own should be well tied, okay, so you think maybe I can use hugs, which is another has to, which is has to interpret the wrong, I think, anyway another has to type checker, maybe that's more helpful, okay, so you pass it to hugs and then hugs says, well it's just x is not well tied, because x is a bool, and then, you know, so there is the error, right, have you tried to keep you, and no, no, I haven't, that's probably a good idea, but the point here is that the only reason that you get, you know, seemingly conflicting error messages, yeah, exactly, because one of these type checkers goes this way, the other one goes that way, and each of them are right, but from the user's point of view it's not helpful, because it's, you know, impression not x doesn't have any problems, the, you know, is just x also doesn't have any problems, so, so, you know, where is the problem, and the whole point of a compositional type system is that it does have this property that if you want to type check a term, you type check the subterms independently of each other, you get back something from each of them, and then you look at those subresults, and they, you know, pass, of course, the termic, like the structure of the term itself determines the type of the larger term, so, so let's look at that, let's look at what the compositional type system would be like, so the first thing we need to see is that if you imagine, okay, if you think of a type checker or a type infer, like something that looks at a term and gives you a type, that's not going to work for a compositional thing, because like in this example, right, suppose that I go down to is just x, you know, you will have type bool, suppose I go down to not x, it would have type bool, so I get a pair of bool-bool, but that doesn't work, it doesn't give me enough information, it doesn't tell me what x should be, like, even if it was well typed, it would not be enough, because I need to know what the infer type of x is, so as we've seen in this example, we need something which tracks more intermediate results than just a type, right, so in this example we want to know something about x from the type of is just x, and we want to know something about x from the type of not x, but the type of is just x is bool, the type of not x is bool, it doesn't tell us enough, so what we want to detect is that is just x is a bool, but also it's not a bool on its own, it's a bool if x is type may be a, and not x is a bool if x is type bool, and then when you put these two together, here we will get a conflict, right, so what we want to see is that the error message should come from the TACU level, and it should tell us that this sub-expression on its own is okay, this sub-expression on its own is okay, it's only the application of one and the other is what is not okay, so the idea is that instead of just types, we assign so-called typings to sub-terms, where a typing is simply a type in a context of monomorphic variables, so when I say monomorphic, of course, I mean it can still have variables, type variables, but this is not for all a maybe, we will see how that all works out, it says x may be a, but for a, it's a, for a meta-variable a, so the a speaks for the food, so the a speaks in the same sense as an existentially bound type variable speaks, so it's a scholem variable of, it's a meta-variable, right? Yeah, so on the food level you can, on the, no, we're not yet at the food level here, let me see, let me see how that works, in the, basically in the, in the food level, you want the x disappear, because on the food level, food type should not depend on x, because food type should be the function from something, something, the fact that it even has a variable in a x, should not leak out, yeah, so here is the derivation rules for a compositional type system, hopefully you don't see why it's so provisional, okay, at least I got this working, yeah, okay, so the first, first rule for variables is that, of course now that we have these typings for subterms, that also means that variables, polymorphic variables will carry a typing instead of just the type, so in gamma, in the environment, we will have typing, right, and then if I see an occurrence of a variable, I will say that the typing of that occurrence is the typing of the variable refreshed, so refreshing here means that I take the typing and I replace all these type meta variables with fresh ones in a consistent way, so all the a's become a prime, all the b's become b prime, right, but they are still, they're still meta variables, they are still not, you know, four-hole bound, and this is not so, yeah, notice how I wrote equals and not member of, like in the inline, in the inline case, the type instantiation is a non-deterministic operation, right, because you take a type which has a universally bound type variables and you replace each of them with the type that, I don't know, the oracle tells you, but here you just mechanically make enough fresh type variables such that you can replace a with a prime b with b prime and so on, so there's no non-determinism here. If you have an application, okay, that's the simplest, right, I just take the typings of f and e, and what I do is I say, okay, the typing of the application is some unification of the two monomorphic environments to see what that means, and of course the two types have to be, like, d1 has to be equal to d2 to a, right, where a is a fresh meta variable, and then if we unify this with this and this with this, we get a monomorphic environment, which is like the union of all the, this is the union of these two monomorphic environments, so here if one of them says that the type of x should be a, the other one says that the type of y should be b, then you get both x and y in this unified environment, and what if you have a conflict, well that's why it's called a unifier, it's not just a union, so in that case we unify the all the types that the different monomorphic environments contain for the same variable, right, so if you have x of type may be a here, and you have let's say x of type may be in here, then you can unify them and you get x of type may be a, oh sorry, may be in, and the for that also means if you have anything else which mentions a, then the a is replaced with in, if you have x of type int and x of type may be a, then this is the step where you get an error, right, this implication, so we will see what that looks like in practice for the is just flash not example, and then for a lambda abstraction we introduce x into the environment, so we need to come up with a typing for x, so what is the typing the type is going to be, so it might be strange but it says that if the type in the context, in the monomorphic context where the type of x is a, it has type a, this sounds like a topology, right, but when you think about it that's exactly what it will mean for a variable to be monomorphic because it will say that it has exactly the type that its type is, so if you contrast this, I hope the next, yeah, okay, if you contrast this with let, in let when we go into the body which is here, we say that x instead of having the type, the typing that came out of the definition, we remove x from that monomorphic environment, so when you make something polymorphic, when you do the let generalization, it corresponds exactly to removing the thing from its own context and the reason that will work and I think, yeah, okay, some more examples of that, so why does this work? You can look at these rules and you can convince yourself that application makes sense, you know, lambda makes sense on its own, you can convince yourself that variable occurring to make sense, this one might look a bit surprising, like why, why do I pass x the same way as I pass into a lambda when I want to type check the definition, but then when I want to type check the body, it gets a different environment, why, why is that, so this makes sense, right, because of course if you believe the lambda root that if you think that that makes sense, then of course if you want to use the same for that because in a recursive let binding, you still want the recursion to be monomorphic, right, but then when you want to use it in the body, you want to make it polymorphic, and when you think about the default value of the hingliminar type systems, let polymorphism, right, otherwise if you just really just use STRC and not get too far, and so, so, so why does this, what does this even have to do with hingliminar, right, turns out that this type system agrees with the hingliminar type system for the type, for well type programs, the type, the hingliminar type of a top level term is going to be the same as the type part of the typing that you get with this system, and the monomorphic environment will be empty, I mean of course it's going to be empty, right, because the only way it introduced to add something to the typing is here in lambda and in left, right, because here we extend gamma, and here we extend gamma, right, but then if you look at the, if you look at the rules for the result, you will see that both here the monomorphic environment excludes x, and also here, well this here is more direct, right, that's all we do with it, we just drop x, and again this makes intuitive sense because it corresponds to the fact that the type, or the typing of a lambda abstraction should contain all you need to know about its parameter in type, right, like even the fact that it's called x should not be visible from the outside, right, lambda x to x and lambda y to y should have the same typing, because they're indistinguishable, I mean they're just enough renaming away anyway, right, the type should not know anything about the choice of internal names, so if you, if you follow this to its conclusion then that is the top level of terms must have empty monomorphic environment, so their typing is just a type, because it's an empty monomorphic environment and the type, and then those types will correspond to the types you get with the individual type vector, of course in such a way that all the type variables are for all bound, right, like you need to write the for all by hand that, for the top level of things, so the why, so why, why is this polymorphic, why, what does this have to do with any of that, so it turns out that if you look at the, the rule for a variable occurrence here, right, that rule says that, you know, we take whatever monomorphic context x has in gamma and we refresh that, so there are two cases possible here, either delta contains x or it doesn't, if it contains x that means when I refresh the typing then I'm going to refresh the right-hand side and the left-hand side together, so if x occurs here, right, and then, you know, it will have the same thing on the right side, if I, if I refresh this, then all I get is that, you know, this, this will turn from x at a, then a, x has type b, then b, or x has type c, then c, but of course all of these means the same thing, as soon as I unify two of these, as soon as I unify, you know, x has type a, if x has type a, then a, and if x has type b, then b, when I unify these, a and b will need to be unified, so that's, that's what makes it monomorphic, right, because a monomorphic variable is something that has to have the exact same type everywhere, right, but if you, if you end up unifying all the occurrences of, of, of the very, of the type of the, of the variable, and if you end up unifying all the, the types of all the occurrences of the variable, right, then that means you regard it as monomorphic, because you know, if all the use sites are unified, that means they all have to agree on the time. So, does that make sense? So, because I, I, I'm, I'm seeing the, the, the, the, the time, so don't hesitate to, to, to ask if any staff is not, yep. I have a, the first question here, because I, I recall that there was a similar work by, I think, So, you know? Okay. Okay. Well, I can't comment on that, because I haven't, you've been, the, but this doesn't push, okay, but this doesn't push the, the variable types down, right, because all of these rules, they just collect, so this is the, this thing that's given, and this is the, the, bottom up, right, because all of the rules say that whatever typing you get out of the, out of the, the subterms, right, so you get this here, you get this here, you get this here, you get this here, all of these are just things that bubble up, and all we do is unify and we drop some variables to get the typing of the result, but we never push anything down. Maybe we can't, right, because if we were to push them, then we would be back to square one, because if you wanted to push anything down from, you know, the previous sibling of the subterms into the next sibling, then you would have the same in the area. So, so in all of these rules, and that's what makes it, oh right, I might not have stressed this point, so in contrast to the Inliminar system, these are syntax-directed rules, precisely because all of them look like I just take whatever typing falls out of the subterms to come up with the typing of the term, so, and everything is deterministic. Okay, so, so if you have x in the context of itself, then that means everywhere where x occurs, they will have different meta-variables, but they will ultimately be unified at the level where they meet, right? So the worst case, like the x at the biggest subterms, that will cause a lot of computation. And so, so in that case, I mean, okay, okay, we're starting again there, because of course, I want to show you the same example with these just and the not, you know, what it gives you with this system. So in that case, what happens is that, yes, it really, it becomes the, the top level expression becomes the one where you get the type error, right? But it will tell you exactly which two subterms, I mean, it will not tell you these two subterms, it will tell you these two subterms that on its own, this one is okay, on its own, this one is okay, and it is true, that's the thing. When it tells you that you have an error here, it really is true that you could just cut this, put it in a top level thing, and it would be well typed, you could cut the other one, and that would be what I just found, right? So okay, but what about lab polymorphism? So we've seen, I hope I convinced you that monomorphic variables are correctly tracked by this system. So what about polymorphism? So if you look at lab, when we go into the body, we drop x from the monomorphic environment of x. So whatever typing falls out of the definition of x, it might or it might not contain x. Can anyone tell me what it depends on whether the monomorphic environment of the definition will contain x or not? Exactly, exactly. So x is recursive because then this rule will introduce the environment of x into the environment of the definition, and as you see here, we add x as a monomorphic variable. Yes, exactly, exactly. So you have two cases. If the definition of x is not recursive, then x is not going to be in this delta zero. If it is recursive, then it is going to be in delta zero. And we use that fact because we say that that type has to be equal to the type of the definition. So we only allow for monomorphic recursion. So you say lab x equals delta x, delta z, delta z. That occurrence of x imposes a type on x. When you do all the unifications as you go up. But you also get a type from the whole thing, from the delta z, delta z, which would be the E0. And whatever delta zero assigns to x has to be unifiable with the type of the definition. Inside the definition, yes. So lab generalization happens after the definition. So lab x equals something like that x equals E0 in E. In this case, x is monomorphic in E0, but it's polymorphic in E. That's what it's like in the universe. So that means in E0, when you see x, you want to impose a restriction on the type of x. But when you see x in E, it doesn't, it can't possibly, like, okay, let me do it another way. If you say lab x equals something in something, compositionally, it means exactly that whatever I write in the body, it can't possibly mean that the definition of x was wrong. It can mean that the body is wrong because it uses x not as it was intended to be. But it can't mean that the definition of x was wrong because I can cut the definition of x, put it in the top level thing, and leave out time. And that's why we remove x after doing all the unification to ensure that it agrees with itself. So after we know that this equation can be solved, then we know that the recursion, if there was any, was correct, or well-tyed. So after that, we can just drop the x completely. And that means when I use x in E, it will have some type P in delta. And if I refresh this, any meta variables that remain in T will be refreshed, right? And there's not doing anything else in delta to be refreshed at the same time, right? Because x is not in delta. Okay, so in the example with the is just x not x, right? If x, suppose that x is lab bound and polymorphic, right? It's like lab x equals x in not x is just x, right? That should time check, right? We all agree on that, I hope. So why does it time check? It time check is because the typing of x in the body is going to be A, right? And when you refresh A, you get B. When you refresh A again, you get C. So you get B equal to maybe A, and C equal to in, sorry, who? You can solve this, right? There's no conflict here. It just makes B equal to maybe A, just makes equal to who and you're done, right? Yeah, yeah. So those are new type, new meta variables applied consistently. Yeah, it's a substitution from type variables to type variables in a consistent way. It's a fresh variable. So that's why this implement lab polymorphism and that's why this has anything to do with in-limit. They're worried not for this. It could be very different types. So, okay. So now... You don't have polymorphic lambdas. I mean lambdas are, even in Haskell lambdas are not polymorphic. Oh, so like why are lambdas not polymorphic? Oh, okay. Well, that's easy. That's because here, which is a lambda x to x, e, x is added to gamma as a monomorphic variable because it's added with the monomorphic environment where x has type A. So given that x has type A, x has type A, and in this environment, we type check e. So what we get out of this is a typing which might or might not refer to x, right? If you have a lambda which doesn't use like a constant lambda, then x will not be present here. If you have a non-constant lambda, then x will be here, right? And that's why your two cases, either x is not in this delta, in which case you just say, okay, t, which is t1 can be anything. It can be, you know, constant function doesn't restrict its argument type, right? Which is a fresh type variable. It's the same variable as we use it. Or it does restrict it, in which case, you know, we know what the type of the argument is because we get it out of this delta. Sure. Okay, so here we do monomorphic introduction to do the, to account for monomorphic recursion. And then we drop it to account for generalization. Yeah, here, because we use that to, sorry, that is your double prime. What's monomorphic lambda? What's polymorphic lambda? Well, we don't have polymorphic lambda. Well, it would be, it would be lambda. I mean, yeah, I don't know. Yeah, but what would it be? What is polymorphic lambda? Yeah, but what would it be? I mean, I don't know. What would it mean to have a polymorphic lambda? What would be the type of, yes, it's a, I guess, okay, it's a shenanigans question. And I agree with maybe the question, whatever that means, is if you have, if you have a lambda abstraction, at the end of the day, you have to say it's type as a function type, right? It's going to be a to b for some a and b. So what would a be if you allow the parameter, the x, to be used at different types? Yeah, they only make it. I mean, in rank to a higher rank types, it could be, yeah. Yeah, but, but in, yeah, but in, in this, I mean, it didn't, like, it's not, it's not that you don't want it, you couldn't even, yeah. Yeah, yeah, but you could, you could de-sugar the morphic let's into a higher ranked lambda. Yeah, so that's one of the things. In, in, in, in alienation, let needs to be part of the language precisely because of land generalization. So you can't do the old trick that you can do in untyed settings where you say a let is a lambda applied on the definition. Yeah, yeah, well, because, yeah, because it's one morphic and let is polymorphic. Exactly. So, okay, so, so given all this, what does this all buy us? So to demonstrate that, I did a quick implementation of, of the, of this typewriter, which is on GitHub and, and so this is a link. So I'm going to put the slides up and then you can click on this link and it takes you to the GitHub page for it. So de-planetation has two parts. One is a linear, you know, old standard in-unit type checkery. It tries to be as standard as it can be and it tries to share as much of its implementation with the compositional one because the idea is that you should be able to look at just two modules, see the difference and the rest is shared. So this influence type checking for this model language, the one where we only have, the one which also contains pattern matching and data constructors, right? It has a concrete syntax that has a parser and a pt printer. And this is the point I guess where I should thank Vicky for helping me out with it because the, the, the front end was, was pretty much none of these things. When I was done with the, the type checker part. So the fact that you can actually enter programs and you get many better messages was done by her, which is really important point of the demo experience. But for the concrete syntax, so indentation based, because I wanted it to look like Haskell, right? As, as like a subset of Haskell. In indentation based parsing, don't do it. It's a nightmare. It's horrible. Yes, seriously. So mega, what's it called? Megaparsec? Yeah, Megaparsec and trifect both claim that they have some add-ons which allow for that. There's a very old Parsec 2 based, something on top of Parsec 2, which claims to do indentation based parsing. Of course, you can try making your own, your own lexer, which adds these virtual tokens. Each of them will fail in different ways. It's not worth it. Seriously. So what we ended up doing is we use Haskell source extras to parse as a full Haskell program. And then we parse the ASP by just failing on anything which is not, you know, variable or application or like our case. This is like, seriously, if none of this theoretical thing matters to you, the one takeaway message is don't do indentation based parsing. As for the actual representation of types and unification, it uses the unification FD, which is a package on hacky. It's for this kind of thing, but even that didn't exactly work out. So it's based on that. But I have to use only half of it and we implement the other half myself. The big difference is that I need the immediate writing of the meta variables. So the way that unification FD works is that when you unify two terms, right, you get a term, and then later on when you try to flatten that by bonking, I guess, like taking out all the type variables, it might be that at that point you learn that, oh, but actually it had an infinite type. It had an of course check violation when it was unified. So unify here, you get something which is not an error. And then later on when you try to use that, it turns out, ah, but it was actually an error than functions before. So that's not good if you want to get type errors which are very precise in the location and where you have a lot of context that you want to include in the error. So, and the other thing is that my version has exclusive zonking. So I don't know if you guys you know what zonking is. I don't know if that term is used much outside GAC. Zonking is basically when you take types which have meta variables and then you replace each meta variable with whatever term it was actually sold for. So if you imagine that the, you know, you're doing your type checking, you're doing your unification, and each unification step will mean that some meta variables will need to be replaced with some other type, right? But if you represent the meta variables as references so that you can easily replace them instead of having to be like a pure substitution, then you will keep these variables. You have a reference everywhere. The reference will point to the right type but you still have a reference. Otherwise the unification will need to be able to, like you make a mutable type where the term itself, I mean the type itself is mutable, you know, not just the reference. Zonking is when you then follow all the references and whatever it ends up, it will replace it. So yeah, so the way this works is that there's a type class for type checker monad because the linear and the compositional type checker basically do the same thing. The difference is what they unify and what they do with the results but how they unify it and what does it mean to unify two types that share. So a type checker monad is something where you can make up fresh type variables. You can, I mean, fresh meta type variables, right? Because you might not even have type variables in your system. In the composition, oh actually, have you guys noticed that in the compositional type system we don't really have type variables because we don't have polymorphic, like we don't have quantifiers, right? We don't have for all types. We only have types, monomorphic types. They can just contain these things which we keep replacing, we keep freshening in it. But if you want to have classic polymorphic types, you can only do that at the top level where you have an empty monomorphic contact because then you can just take the right hand side and apply a, you can pretend that there's a for all. So these are all for meta variables, right? So you get fresh meta variable, you can read whatever the current solution of a meta variable is, you can replace that. So these are like mutable operations and then when you want to get out of this mutable world you can zone as much as you can. So this of course still gives you something which can have meta variables because you might have unresolved variables which still don't point to anything else. I don't think we will see why, I don't think we will see why. Because like I said it's mostly shared between the two type checkers and it's done by a type which is parametrized over how you represent the context. So this would be basically the gamma, what is gamma, right? Which is different in this case because one in one case you have polytypes in the other case you have typings, not too very close. How do you represent errors? How do you represent source locations? And then the whole thing runs in SC. So we have the usual SC token that we have to carry around which means that the type checker is both performant because it uses SC revs and pure from the outside. Yeah, so yeah gamma is different, that's why it's a program. So what does this all give us? Well let's go back to the original example, right? So you know we don't have syntax sugar for tuples and everything so it has to knock the error instead of comma but well it's a small problem to debate. Oh maybe, no I think that the point is here, yeah okay. So if you try to type check this it will tell you that it cannot unify Boole with maybe A when unifying X. So the problem is in X is type because Boole and maybe A cannot be unified. So these would be the types which if this would be list of Boole to list of maybe A, right? This is the leaves that can't be unified and then it tells you the actual types which can't be unified. And then this is where the important part, this is the thing that JSC can't tell you which is not pair not X has type Boole to pair Boole to Boole, right? Why Boole? Well because that one could be solved. So it's yeah this one because this is the A to pair Boole A but because this is a Boole we already know that this one is solved already. It's just X has type Boole. So if you don't look at the last line everything is fine I want to apply Boole to something function on a Boole. However what is their view on X? What do they think of X? And look pair not X, things that X is a Boole and is just X, things that X is a maybe A. And that's it. So here X is where the problem is. So I think this is the correct error message for this example and in general I think these are the error messages you want to see. As another example, I mean this is just a this is just a quick example of some functions that I implemented for testing. So you can see that the you know the types you get really agree with the these are all various definitions of functions like these you know like one of these is done via a fixed point combinator. The other is done by just saying undefined equals undefined. One of them is undefined equals let X equals X in X. So you know it tries all three usual ways of the bottom and then the others are well the standard definitions. So just to give you an example, so for the and this is the thing running so this is the definition of you know maybe Boole and pair. Of course we need some infrastructure right because you don't have a private so we need to define what Boole is, we need to define what maybe is and what pair is and then we can say okay so not is the lambda which takes b to a pattern matching is just the lambda which takes m to a pattern matching and then did they make a pair? I think in the other side it's called not a pair. Yes I think that yeah and then you know because it's the same area so you just want to slide right because you that not to pair not X, Boo to pair Boo is just like Boo which is a okay but then here you have a problem. I mean of course I can also show you the definitions from the working example but again it's completely standard so that's the idea right that you just write something to that as well. So yeah okay and so that's basically it so if you want a lot more details the two sources I can recommend one is a paper from 2001 which is how I encountered this whole thing, composition explanation of that with debugging of typers. It defines this compositional type system and then it shows how you can use you know this kind of results by putting a fancy UI on top of it which would then allow you to you know like okay it says it has no pair not X but then you might want to understand more why it has this type and why X got this type so you can then focus on the sub expression like you can double click on just not X and it will show you the typing of not X in the same format and that's and it works right because again this compositional so it's meaningful information that you get by tuning down right and the other one is by yours through it that was my master's thesis that's how I got involved in this whole thing. The one I did was I took this system and I add the type classes to it which is of course something I completely skipped on this one because the idea here was to have something small and self-contained but type classes work so you can add type classes to this system and then you get a very classical 98 light language and then you can tie that into a ghc like you can take the front end of ghc and the back end of ghc and put this in the middle and then do this kind of type checking and so the implementation itself is quite big problem it was done for 6.12 I think like I don't think you can compile that today but if nothing else you can just take the take the the modern implementation which is very small and self-contained and maybe get some ideas out of that and that's pretty much it. So is there any data dependency when you run different branches of the tree? So is it possible to parallelize some trees in that? Yes yeah okay so okay the details of that basically depend on how you implement the unification so okay if you wanted to make something which parallelizes by construction you can just implement naive pure substitution because then you really don't have any that the only communication within the branches you have is when you're done with both branches yeah yeah and if you use references to represent the meta variables which is what I did in this implementation which is what ghc does and which is pretty much everyone does who wants to you know have some something which works very well in practice um maybe I would go with maybe um yeah because the problem then is that I don't know what like okay I imagine like this is okay this is completely conjecture I think it would work but you might not get the right error you might not get the right points of where things go wrong yeah I mean you always okay well why do you unify yeah because wouldn't it be weird because then you could unify two like let's say two deltas right but what's inside those deltas is not just the view from below because it could also already contain the view from the side because it has been unified the the meta variables already have been rewritten to account for things on the side so so it's good okay so for well type programs does not be an issue because then everything gets unified with everything anyway for not well type programs you would get a point where things don't unify but it might not be the point that you want okay and then there's the copout answer is that if you want to do that you can always implement this using this way if you get a type if you don't get a type where you're done you've done your parallel type checking then it's done if you do get an error you can run the whole thing sequentially yeah and that has been my copout answer for performance questions for five years now because in my defense they asked how the performance of this compares to the linear I don't have any measurements I don't have any any I think but the complexity answers but I can tell you that linear can be slow right theoretically it can be exponential and again just run it with any linear if it type checks you're done if you get an error you can just run it only then run this together you better yeah so in the other class a compositional types a compositional type checker that to some of the two is more complex than just any other with error sure but you want a good error message yeah and it really will not get easier yeah okay so when I was doing when I was implementing the compositional type checker I found that okay so for the linear type checker adding existential it's almost too easy like you can slip your finger and suddenly you get existential even if you don't want that for the compositional type system I think you could do existential um basically by having meta variables that you don't refresh right and that you kind of do it by and something like that right so I understand that for all or or explain it for all it's also normally done this way and you basically assume that some variables have to be created when you do it okay very so the you mean like spoke type variables yes that's pure syntax that that that's I mean if you have existential then original version for or around two that's different way that that's different yeah it's different so but it's invisible it can be also simple so what it was initially required was that you explicitly so that it basically is as a constant in the environment before you start checking right so okay so neither the implementation nor the the rules here in my mind type signatures right so but that one you can do after you have like you can imagine that that's just the last step after you've done your inference and for recurrence the for occurrences you just use whatever is in the signature right again that's the standard technique if you want to have higher rank types and you're saying that it's enough if you can introduce them like if you can introduce them explicitly by end of the thing but then you have expression that is for all that is marked as something that you need to instantiate and you are the additional rule right it tells that this is very actually we have that right you actually use it you need to instantiate it separately yeah so that would be the like this rule basically yes yeah so and unfortunately if you don't do it then you have a different problem as far as okay so again it seems to me that that should be doable by by not so you either write this exactly with that this declaration or you can post it in your identification but I think it's possibly so published yeah yeah okay okay wait that's different things that yeah type inference for rank two that there's a paper you can find where they basically prove that it's possible but no one has implement yet for any practical language jc implement rank two if you turn on rank two types it's the same as turning on rank n types so you always have to give yeah so if you if you don't don't go into semi-unification which is kind of there already a problem that's not ah then you have just the instantiation rule yeah okay so basically the type signature breaks the cycle yeah yeah no sure sure yeah the type G80s break the problem because yes basically give the signature for each variable which in this case constructed so you're saying that rank higher rank types should fit into this without planning if you allow yourself to do you know that sometimes you would have environment so you change the declaration and you have the yeah but that's I mean but that's that's very easy that's that's like standard right like even even if you don't have higher rank types you might want to have types and then and then you don't want like you know one of the in implementation of the first things it does because it finds the strong connected components of definitions because you know because you only do monomorphic recurrence that means and everything which is in the same group will have to do monomorphic like mutual monomorphic recurrence so that's and breaking that cycle is actually even easier than than building the the graph and so that that's pretty much for free the the real question is just because you do that is that really not so what would be the typing basically if you think about it if you declare the type of function of two variables a and b and you have a pre-declared signature then we basically say that a and b are not variables are constants with the type that is for all test of each issue can I just you have a function that is something like for all gives us what it does is the function takes the parameter and then checks that we have g of one and g of so you need to go a bit one step further from the declaration which is basically that when you get here you are saying are saying all the variables on the left side this is probably monomorphic types which means the g is the constant here or is or the g is already in the environment which means that whenever the g happens to you you will instantiate it so you will instantiate it with an a to a but that's for the you're saying now in any manner you think yes yeah okay but that's yeah but that's yes that's standard but then here right what would be the monomorphic what would be the typing of g such that when you and I guess it would work yeah because g could simply have a typing you doesn't mention g in the monomorphic environment okay but that gets tricky if you want to have a b to for all a a to b right because then if you have another type which is not this one is not monomorphic over so then you want to include something about g in the typing but it would need to be something which only prescribes the agreement on all variables you don't have the concept of variable groups yeah because I don't even have five variables right yeah so okay so then then what you need is explains for all n type to know whether alpha or beta in this case is the local type or not local so you need to distinguish them for instantiation but you may have a signature that is all yeah the signature is not an issue yeah so if you say the f has a type that is actually for all if you think about yeah yeah for all at every point where you yes yes yes but you're you're you're you keep which is okay that's what you know yeah that's what player and types are and that's how you know the question is how they fit into this framework or you don't even have time for you only have math variables and you have no way of like yeah and you have no way of capturing just partial restrictions because you either put g into the monomorphic environment of g or you don't like that's basically at least as it is that's the only degree of freedom because I know that there are type checkers that basically use this experience for all and then they delay instantiation until beta is actually unified which means that maybe there may be anomaly that your function is actually becomes run to polymorphism or higher rate polymorphism in the in the basic algorithm is not because it delays the initial variation of rules and here I think you instantiate it on the occurrence yes not before not at the moment of yes but that I mean but that's like crucial component of making this I mean yeah this one yeah because you know that's what that's part of what makes this syntax directed and part of it makes a composition of that that you do a fully determined instantiation well it's not real instantiation it really is a just a refreshing here the instantiation the choice of the instantiation will simply happen when you unify you know the different use sites but the you know but the the occurrence itself doesn't doesn't yield any any any decision on on how we will instantiate it because then you get back exactly to me you kind of in this environment on the left so that's the big change okay