 This workshop will be a practical introduction to Haskell Gadets. Before a lot of the introductory matter, I just want to drive everyone's attention to these instructions up here. There are some dependencies for the last exercise that we'll be doing during the workshop. They shouldn't take very long to do, so if you start them now, it should probably take 10 minutes or so for all that to compile. But you might as well get started now. So people can go off and do that. So thanks very much for coming today. Let me start off introducing myself a little bit. I am a fourth year, starting my fifth year soon, a grad student at the University of Pennsylvania. I spend a lot of my time thinking about these languages. These are the three big, dependently typed languages that I like to think about. At a curiosity, how many of you were at the Idris talk this morning? Wow, okay, cool. So I don't think we had a sort of a way of knowing this until after it all happened, but I think this workshop is the workshop that it'll end where that one began a little bit. And so if some of you have some questions lingering, especially from the beginning about the equality, it's actually very closely related to what we'll be doing in this, and so that they might link up. Anyway, so I think about these languages, but I think a lot more about this one. And what I'm, my whole research agenda is figuring out how to get the dependent types into Haskell. And so, again, as a counterpoint to Brian's workshop this morning, so Brian was talking about how one of the nice things about Idris is that it's simpler because it was sort of built from the ground up with dependent types. That's certainly something I agree with. But that doesn't mean that there's not a place for it also in Haskell. These are different languages. They have different ways of working and different type inference characteristics. And so I'm really looking at sort of how to do dependent types in Haskell. Things are going fairly well. We're going to have a, with the release of 7.12, there's going to be sort of a big step in this direction. And then with 7.14, which would be, I guess, in sort of late winter 2017, we might actually have real dependent types in Haskell. And so that's at least my hope. Okay, but we're not going to talk really that much about dependent types today because we're just going to start sort of at the shallow end of that with Gaddits. But before we even get there, let me learn a little bit more about my audience. So before we get to the items I hear, how many of you have programmed in Haskell before? Okay, awesome. How many of you have used Gaddits before? Okay, so I know I haven't even defined my term yet. There is a Haskell extension spelled exactly like this. And so if you've programmed in a Haskell file with that extension on, so we can sort of start there. So how many people have programmed with that extension on? Okay, if you don't know what these terms are, that's okay. This is just sort of get an idea of where everyone is. How many of you have used promoted data types, the new data kinds extension? No one. Okay, okay, a little bit. Type families. A little bit. Singletons? No one. Okay. How many of you have programmed in these languages not counting the workshop this morning? Okay. So that's sort of what I expect. A lot of people who have done some Haskell, but maybe not use a lot of these features. And like I said, that really is okay. I'm not expecting people to even know those. Let me also line out the goals of this talk. I would like by the end of this for you to understand the basic mechanics of how gadgets work and to be able to sort of work with them a little bit and probably most importantly understand why they're really cool and why you might want them. What this talk will not be able to deliver on is being able to walk out of this room and being able to take some code base that exists and say, wow, I'm going to use gadgets all over the place now and it's going to work even so much better than it does today. That takes a long time to build up that level of skill. We're not going to get that far today. And so we're going to be working with a code base that already has a lot of gadgets in it and I'm not expecting you to be able to sort of design that from the ground up just after these two hours. Okay, so finally, what is a gadget? A gadget is a generalized algebraic data type. That's what the letters stand for. I don't find that there's all that much meaning that you can plumb from these words though. So I'm just going to keep calling it a gadget and it's just a thing and we'll see what it is and that's what the words stand for. The idea is that we're taking the data types that we already know something like the maybe type that everyone uses, right? And we're generalizing that in a certain way and we'll see how that works. But again, there's not all that much you can pull out of these words. So right away, the interesting thing that gadgets allow us to do is they give us more compile time checks than normal ADTs. So Haskell programmers like to, you know, tout that maybe is so much better than null, right? You know, Java's null because you have to always check if a maybe is nothing before you extract the data out of it. So this gives you greater compile time checking. Gad is just sort of bring that one step further, right? And so we're used to how in Haskell, our types give us more compile time surety that our code works. And again, Gad is just one further step in that same direction of travel. And so if you liked going from one of these languages down here to Haskell, you might also like gadgets. Okay, the sort of the domain of study that we're going to consider today is a Lambda Calculus interpreter that I've called Glamda. Not only because it's glamorous, but because it uses gadgets. And this is a simply typed Lambda Calculus. We're going to see demonstration of it sort of toward the end. But the idea is that we're going to be building up this interpreter in Haskell that interprets this other language. And, you know, you could ask, well, you know, this is entitled a practical introduction. Is this practical? Well, Philip Greenspun says, yes, it is practical. And when you think about it, large programs do contain another programming language inside of it, right? You think of word processors. There's a macro language hidden in there. You know, Emax has its own very fully blown, glorified programming language inside of it. Spreadsheets have programming languages inside of them. Games often have sort of mod packages that you can program in. And so this is practical in that way. But beyond just those applications, gadgets have other applications as well. You can, in just the same way that we're going to be building sort of a type safe Lambda Calculus interpreter today, you can also use it to do type safe database access. So you can have sort of your program check its types against an SQL schema. You can use gadgets to have verified data structures. So there's, at the end of the talk on the last slide, there's a link to a tutorial about how to build a verified binary tree that is always balanced, that if you try to unbalance it, your program won't compile. Gadgets can be used for generic programming so that you can sort of do a little bit of dynamic checking in your program and still have that be type safe. And tagless programming, it's a little hard to explain. It's a way of sort of making programs actually a little bit faster. This is an efficiency thing. Okay, so let's actually see our first live gadget. So I'm going to jump off to Emacs. Where is it? Okay. So here I am. I will be typing right into this. We're going to be writing, whoops, that's not what I say. I say language first. Okay, so we first have to turn on the gadgets extension to Haskell. And then the obligatory that. Module header, does this compile? Is my system working? Maybe. I certainly hope so. There we go. You'll see here that I'm using the new GHC-710. That's not really necessary. Gadgets have been around for a long time. I don't think there's really anything I'm talking about in this talk that wouldn't work as far back as 7.4. Some of the stuff that I'm using does require 7.4 though. So let's write a gadget. And then I'll explain how this works and why it's called STI. Okay, does that compile? Yes, it does. So there's a couple of weird things going on here. The first is the syntax. By raise of hand, how many people have seen and sort of understand this syntax? What's going on here? Okay, so that's most people. What's going on here is that we have two different constructors to a data type STI. This isn't all that different from a normal data declaration except that instead of just saying that the two constructors are Sint and Sbool, neither of which take any argument, I'm actually describing what the return types are of each of these constructors. So when you define a maybe type, let's write in the declaration for maybe. So we have a comparison here. So in a maybe type, we have these two constructors just and nothing. And we say what arguments they take, but we don't say anything about what they return because we know that they just return a maybe A. Well, with a gadget, this is the generalization. We've generalized that to say an Sint doesn't return an STI of any type tie. It always returns an STI of int. And Sbool will always give us an STI bool. And what that allows us to do is in this zero function, why would I say five? There should be zero, of course, is I can say I can use the zero function to get a zero of the type that I give it. So the reason this is called STI is because this is actually a singleton type in that if I have an STI int, then the only possible value I can have is Sint. And if I have an STI bool, the only possible value I can have is Sbool. The fact that it's a singleton isn't really all that important for now. The only reason I'm really saying that is to motivate why there's all these S's around. So with this, we can actually write this sort of more interesting function zero, and the return values of these two different clauses are different. The return types of the different clauses are different. In one, I'm returning zero a number, and in the other, false, and yet this still type checks. We can't normally do things like this in Haskell, at least not without gadgets. So let me add... Actually, I don't even need to do that. Let's switch back to keynote for a second. And by the way, I'd love this to be interactive, so if there's questions, please raise your hand. Let me pause now. Are there questions? Yes? Right side of the arrow. So what is STI? Return a type? So this is just... STI is sort of an indexed type. It's sort of like maybe in that we have the name of the type STI, and then it's given some parameter. And so just like you could have a function maybe a arrow a, that's sort of the way that this is written. Does that answer your question? Other questions? Yes? Sure. So if... Oops, that doesn't make any sense. So if I try to compile this, I get an error that says that I can't match this type A with Bool, right? In the second clause that I said I want to return a Bool, but Haskell is expecting me to return something of any type A. And the difference is that with zero, by doing a pattern match, I've learned something about that type index tie. I've learned that it has to be Bool. And so let me go from here over back to Kino to sort of explain a little bit more about what's going on in this example. And so here is the same example. It's also extended with maybe. And what we can do here is when we pattern match, we learn something about tie. So in particular, after I've pattern matched against S int, we see here that index tie has to be int. Because we see up toward the top here that, well, an S int can only be an S tie of int. We've labeled the return type in our syntax here. So that means that when I pattern match, I learned that tie is int. And so that means that returning zero is the correct... The value zero has the correct type there because I know if tie is int, that tie is int. On the next line, by pattern matching against S Bool, I learned in that clause that tie must be Bool. And so producing a return value of false is just fine. There we go. Down here, I learned that tie has to be maybe of something and so I can return nothing. So by doing my pattern match, I get that information out. Yes? I have two questions. So before you added S, maybe, the TY parameter was a phantom, made a phantom type, right? And then can you pattern match in a case expression and have the same result where the return... What's on the right-hand side? Yeah, this works just as well in a case as it does in a sort of function pattern match. Was there another question? Okay. Thank you. Yeah, you're welcome. Yes? So is this a way to make a function to return different type? Yes. That's one thing it can do and that's an important thing it can do, yes. So this still... I realize this may be a little confusing, so now I'm going to take the exact same information and present it in a slightly different way and maybe if some people are struggling a little bit, that might capture you. So here I've written... This is the exact same type. It's just using a different syntax. And so here I'm not using this where syntax. I'm using sort of the more traditional data type declaration syntax that Haskell has had for a long time. And what it's saying is that to call the sint constructor to be able to build an sint, I need to satisfy the constraint that tie equals int. So this twiddle, when you saw this on the last slide too, that's just Haskell's notation for type equality. So when I say to the left of that double arrow that tie equals int, that means I can only use sint when I know that tie equals int. I can only use sbool when I know that tie equals bool and I can only use s maybe when I know that tie is maybe of something. Sorry, so in other words, if you want to have implementation of zero sint and you cite it equals to another string, then you have to compile that error. That's exactly right. Yes. Yeah, if I did anything else sort of on the right-hand side, any other types, I'd get errors. This is not making sort of Haskell dynamically typed. It's just sort of allowing us to make decisions and have different types in different clauses. Yes. Dropping. I don't know. So it is in some sense because nothing, it doesn't really matter what it's a maybe of because nothing works at any type. Yes, it does. That's a great question. Yes. So s maybe, this is sort of going in a different direction than we're going to focus on today, but s maybe is also an existential, it's called an existential data constructor in that it sort of does package up this tie prime with it and when you pattern match, you get that back out. Yes. This is identical in every way and in fact you would need to enable the Gadot's extension to write this. And internally within GHC, it actually converts the other syntax to this. Twiddle. Twiddle. Yeah. It's enabled with the GADT's extension or the type family's extension. So I actually guess you could probably come up with some list of extensions that would get this to work and wouldn't include Gadot's, which is probably a mistake. Okay. So the other thing with these constrained constructors, yes. Oh, you know what? You're right. Yes. It should be maybe s tie of tie prime. Yes. You're right. Yes. That's a bug in the slide. Thank you. I can't really sort of edit it very easily, but yeah. It should be after that double arrow. Oh. Right. Yes. That's not cut off. That's actually just my mistake. But that's what if you write that, it should hopefully work. Can anyone report back that it works? Great. Oh. Yeah. Actually that would work fine too. Yeah. But to be equivalent to the previous slide. Yeah. So with these constrained constructors that we see here, with constructors with sort of constraints to the left of the double arrow, when you pattern match on the constructors, you get those constraints back and you can use them. And so that means that here, after pattern matching on s int, we learned that tie is int. And here we learned that tie is bull, and here we learned that tie is a maybe. Exactly the same behavior. And we can just think of these constraints as becoming available after the pattern match. And it turns out that constrained constructors are useful beyond just gadgets, here with sort of equality constraints. You can package a show constraint in a data type and then unpack it by a pattern match, which can sometimes be useful. Okay. So the takeaway from this whole section is that pattern matching a term gives type level information. So the pattern matching is still on terms, things that are around at runtime. Any sort of pattern match has to be against something that's going to be around when your program is running, because a pattern match is a decision that your program is making. But in the context of that match, you get type level information, which allows your type to sort of be a little bit different on the right-hand side of that pattern match. And that is the power of gadgets. It's that these sort of runtime decisions can affect compile time information. Okay. So with that, it's time for you to take this and extend it a little bit. And there's instructions there. That brings you to sort of the front page of the GitHub repo. You can scroll down and click on Exercise 1, which has all the details there. So let's get to work. It should maybe, I don't know, six or seven minutes, so we'll reconvene. But let's go over the solution. I think we'll start with wrap. So walking around, I saw people get sort of this part pretty good, because that's very similar to the maybe case, which I know isn't up here. I guess I'll throw that in just for completeness. And if we get a wrap here, well, now we're a little bit stuck, right? We don't sort of have a zero element of this type. So the idea may be not very clearly stated anywhere, but sort of what can we do? These are sort of only one thing we can do at this point. We want a wrap something. So we know we're going to have to call the wrap constructor. And now we need the zero element of A, sort of the thing inside. Well, so we just use recursion. And then, oh, that's from this won't work. I'm getting errors. It still doesn't work, right? But that code up there does. And so that's the wrap example there. List is just like maybe. There's really no difference, because we have a nice zero constructor that we can use. That still works, hopefully. Unit is very straightforward. And then it's sort of strange, but there's nothing all that unusual about it under the hood. So arrow is a type that takes two arguments, unlike list or maybe. So list takes one argument, maybe takes one argument. Arrow, as a type, it takes two. In this case, A and B. And so we can form the type just like that. And I'm pretty sure I heard whispers of the correct sort of word that we need to use in the answer. What's a zero function? How can we build a zero function? Const, yeah. So we don't care what the argument is. We do care what the result is. So this is just going to be the constant function that returns the zero of the result. And so here we have this whole sort of plethora of different types that we can return based on what we can pattern match on. Questions about this? Yes? Okay. So let's just do this so we can see what we're doing in the end. So if I say, I'm not sure exactly what you tried, but if I say zero of S wrap of say S bool, oh, no instance for show. I said this should be an instance for show. Oh, yeah. An int is going to have a, no, you wouldn't call S wrap on 10. Because when you, the argument to S wrap, so when you call zero, right, it has to be one of these S tie things. Yeah, it has to be this type representation. Other questions? Oh, sure. Yeah, sorry, scroll off. One sec. There we go. You can derive show on this, but not the way that you want to. So if you just say deriving show after S tie, you get an error because that deriving mechanism doesn't work for gadgets directly. But Haskell also has standalone deriving. And then that should work for a type like this. So I say deriving instance show S tie of tie. I think that will work. Yeah. So now we have a show instance. Yes. So for all the data constructors from S maybe downwards, the left, the first argument is already wrapped into the S tie. Does it have to be that way? I mean, could you just have an A2, for example, that's maybe A2 and S tie maybe A2? Yes. Just for this example. Right. You can. But then you're going to get different information out. So for maybe and for list, that would work just fine because we don't need to know anything about that type that's inside. But for wrap, that wouldn't work because then here on the right-hand side of the wrap case, we sort of have nowhere to go. Right. Okay. I think let's move on. Okay. So back to Emacs already. Okay. So I want to talk a little bit about type inference around gadgets. In Haskell, we like to often put type signatures on our functions because they're good documentation. It's good double checking. But there's sort of this general property that we all believe in the background that we don't really need any of these type annotations. That Haskell should just be able to infer everything. And that's true when you don't enable any extensions. But we've enabled some extensions. And so that's not true anymore. And what I'm going to do is I'm actually going to comment out everything except one bit of this. And even that one line is going to fail to compile without a type signature. And so we're not going to sort of plumb the depths of this error message. I will just point out that it complains about T being untouchable. I could give a long lecture about what exactly that means. I'm not going to do that. What we care about is when you see that error, it means add a type signature. That's sort of the... Whenever you're working with gadgets, you will encounter untouchable problems. And 90% of the time the answer is add a type signature somewhere. At least if that doesn't fix the real problem, that will get you a better error message so that you can figure out what the real problem is. But there's a good reason for this. It's not just that GHC isn't smart enough or that no one's bothered to implement this. It's really impossible. And that's because zero, as written with just this one equation, there's two possible types that it could have. And there's no way to know which one is right. There's actually more than just two, but there's sort of two obvious ones. The first is the one that we were giving it, which we know already. That's a perfectly valid type for this zero. So that compiles with warnings about missing patterns. But the other type that I could give is this one. And that will also compile. And at a first glance, it might look like having a tie here instead of bull at the end. Let me write them both next to each other. It might look like the second one is somehow better than the first because it looks more general. But it's not actually more general. This second one, for example, I could... Wait, I want to talk about the first one. This one could become something like this. If I instantiate tie with int, I can specialize the first one to s-tie int to bull, whereas the second one does not specialize there. And so that means that these two types, there's not one that's more general than the other. They are incomparable. And so there's no way GHC is going to choose between them, so it won't choose. Yes? This seems like a very significant... Yes. I know how seriously was that tradeoff considered. Well, I think it depends on what you're saying. So we have not sacrificed that for Haskell 98 or the Haskell without any extensions. Sure. And so at least my thought as someone who adds extensions to Haskell is that we need to keep that core working. But this gives us a lot of expressivity. And generally, you can get there just with top-level annotations, which in practice most Haskell programmers use a lot of anyway. And so it's sort of a pragmatic standpoint of this actually doesn't bite so much in practice. And so it's okay. You know, how much was this considered? You know, I know it's... In the papers about gadgets, they definitely talk a lot about, oh, we're losing principal types here. Yes? This is a very, very lucky... And then come out by the moment you start doing fancy things with your type system of the kind that you're just showing. But those of you who don't know what this means... Oh. Sure. So a principal type means that when you write down an expression, there is one best type that you can assign to that expression. And when I say best, I mean most general, more than any other type that you could give. So if I write the function... If I write that, for example... The best type would be A to A. The best type is A to A. But another perfectly good type is int to int. But the principal type property means that there is a best. And for when you have gadgets, that's not true anymore. So... To be clear, it's a... Because the Haskell type system, even without extensions, is... See, what was the equivalent? It's in the system out category. It's not actually in the lower category because of... For all... So I guess, actually, for all refers to extensions, doesn't it? Yeah. So if you're thinking of sort of higher rank types, which I'll touch on right at the end of the talk, but yeah, that also gets rid of this, gets rid of principal types, but that's not Haskell 98. Yeah. There's polymorphic recursion, which does some weird things, but I don't know if we're gonna go there now. I don't know if that's in the Haskell report or not. Yes? So you kind of explain what principal type is today. Why do we need... I think originally, when the idea of principal types sort of started and the Hindley-Millner type system came out, which is what Haskell is based on and what OCaml is based on, the idea of being able to write a program without any types at all in it was very attractive. You could just write it and it would be a strongly typed program, but you don't write any types. And I think that the programming community has moved past that in some regards now and that we realize types are good documentation. And it's okay to put them in. But what's interesting about principal types is it means you can write a program without any type information and it still is well-typed with a very specific meaning. And it doesn't depend on sort of guesses that the compiler might make. Robert, it says, more interesting your types, the less fun they are to write down. But here's... Yeah. It kind of makes sense you want to be able to avoid it. Yeah. Sure. I guarantee that you have a... I don't think it... I mean, the statement of principal types doesn't really talk about an algorithm for it, so I don't know if it does that. But I haven't thought about that. Okay. Let's... Let's play forward. So we saw that untouchable variables mean you need to add a type annotation. One thing I'd like to point out in case someone's thinking it back there is, you know, here it almost looks like that has a principal type. Right? Because on the right of the arrow, the return type, it can't be bool. It can't be int. It has to be something else. Well, it turns out that Haskell has these things called type families, which allow you to do even stranger things in types, and in the presence of that possibility, this still doesn't have a principal type, even if you have multiple equations. So the bottom line is, when you're doing a gated pattern match, you don't have principal types, and that's why you need the type annotation. Okay. Let's see. What's coming up next? So once again, this is the takeaway from that little section. If you see t is untouchable, add a type signature. So the next element of Haskell that's good to know about when you're thinking about gated programming is the scoped type variables extension. This isn't something we're actually going to exercise too much today, but if you play around with this, you'll need this. And here we have two different functions, and in some sense they look pretty similar. We have up there, we have the foo function, which takes some argument of type A, we'll call it x, and then there's this felper, and felper also has type A. What's strange is that in Haskell, felper and x have different types, even though they're both called A. And that's because when GHCC is the type signature of felper, it thinks you mean for all A. So it would be hard to give a concrete value to felper there, because it could be anything. It really has to be undefined. In bar, if we have these scoped type variables extension enabled, the only difference between bar and foo is that I've written for all A after the colon there. And what that means is that A is lexically scoped. So in the body of bar, including in the where there, we have that it's the same A. So indeed here, felper and x will have the same type. And so if say you have a helper function inside that does some sort of gadget pattern matching, and you need to give a type signature, this distinction is really important, and this can sink you quickly if you don't know about it, which is why I'm putting it here, because it's a really bad gotcha. And this is one of the very few extensions that GHCC does not tell you to enable. It'll just say you're wrong and smile at you. And then you have to go up and add scope type variables, and then it'll work. And so be aware of that. So sure, so in foo, the two As are just considered totally independent of one another. And so it doesn't know that there's any relationship between felper and that argument x. Whereas just by adding the for all in the bottom example, GHCC then does scoping, and it actually brings that A into scope so that when I refer to it in the type of belper, I get the type that I want. Yeah, that's right. Well, yeah, it sort of starts at the double colon. No. So with scope type variables on, this is the behavior. The scope type, you need to enable the extension and write the for all A to get it to work. Yes. I agree with you. And I've seen people say, shouldn't just scope type variables be on by default? And then there's a lot of people saying, no, but that's not in the standard. The standard says they should have different types. And these are both, they're both good arguments. I'm not actually, I don't even know which one I agree with between those, because I'm a big proponent of standards and adhering to them. Yes. You go first, yeah. So what, can you just omit the type signature? Why do you have to have not nine over there? So the reason to have a type signature is let's say, this was a more complicated example where functions did a gadget pattern match. So then they'd need a type signature and we might need to connect it with the overall type. So, yeah, we need a much bigger example to see why. If you plumb the source code of the Glamda example, it's used a bunch of times. Yes. I was going to say, is it a tree statement that the first one is equivalent to saying where it felt or it called B? Yes. Yes. Is the for all necessary in this example if you were using gadgets? Because I've used scope type variables but I'm happy absolutely because I did have the type signature to work was for documentation and without scope type variables the outer function and inner function they had some types. I didn't have to specify the for all in that case. But it wasn't using gadgets. It's not to do with gadgets. To scope type variables is more than just this. There's other ways of bringing type variables into scope so it sort of depends on the details. And sometimes you don't need the inner helper to have the same type as the outer one and it'll still type check. So it sort of just depends on the details. Yes. Is there any way to get scope type variables with that? The type of bar is different than the type of food. That's not the same type. I would disagree with you. I would say they are the same type. There's a lot of bar. I think we can debate this afterward. So you have to instantiate the type. You basically move the for all. That's why for all is a hair range. No, but in this case there's no parentheses here. So this isn't bar is not higher rank. Yeah. Yeah. After an hour or after open parenthesis or something else. Anyway, we can talk more about it afterward. But yeah. Sure. Okay, let's see what's coming up next. Dreaded bug 3927. Okay, let me undo some of these changes so we're back to a working state here. Okay. Does that compile? Yes, good. Okay, so let's say I want to write an equality function on s tie here. So I'll do this. Okay, and let's I'm going to make things a lot simpler by getting rid of a lot of this stuff. Don't need any of this stuff for this example. Okay. I know I'm not done yet, but let's see what happens here. So I'm going to get a pattern matches are non exhaustive. If you're trying this on your own computers, you might not see that because this warning isn't enabled by default. This is on my GHCI because I can't live without it. But you may need to sort of do a, if you put this in your file, you'll see this. You'll get the warnings that we're talking about. So it says that I'm missing some patterns. Oh, so I'm missing s maybe. Okay, let me add the s maybe pattern. And I compile. Oh, now it's complaining about more missing patterns. So let me add the first one. And I did exactly what GHC told me to do. But now it's telling me my code is inaccessible. This is terrible. This is really annoying. So GHC is right. This code is inaccessible in this last equation. Because what's happening is that when I pattern match on the first argument and I get s int, I learned that tie is int. When I pattern match on the second argument with s bool, I learned that tie is bool. And GHC is smart enough to know that tie can't be both int and bool. And so it says that this clause will never, ever, ever happen. And that's 100% correct. So the error is right. The warning that you get when you don't have this, that I'm missing clauses, is bogus. And it turns out that for reasons I haven't fully plumbed, this was a very hard bug to fix and has been around. So right now GHC bugs are numbered in sort of 10,400 range. That is in the 3,000. So it's been around a while. But this fall at ICFP, there is a paper that proposes the solution to this problem. So I think we can expect this to go away in 7.12. If you start programming with gadgets before then, expect to see this. And so you'll get warnings and then you have to try it out. It turns out that this eek s tie that I've written here is actually really silly. Because by the type signature, I can see that the answer has to be true. I've used the same type index twice. There's nothing I can pass to eek s tie that should give me false. So it's a silly example, but this comes up in more realistic examples also. So what I do when I see this, because I don't want to trust myself, I only sort of want to trust the computer checking all of my code, is every pattern that it tells me to try, I will write it in my code and make sure that I get the inaccessible code error. Which is really annoying and I'm looking forward to not having to do that. Yes? Yes, yeah, that's fine. So just to demonstrate that, and that's in fact what I do in the end, is that. And you'll actually see there's one or two places in the Glam Dissource Code that that comes up. And that really should never, ever happen. This is not a runtime error. This is like something else. This is just code that won't ever happen. Okay, let's see. So with that, oh, heterogeneous lists. Great. We're going to open up a new file for that. So one problem that sometimes people complain about in sort of functional languages like Haskell is that lists are really sort of constrained, that they always have to just have a whole bunch of the same thing. And it can be useful to have a heterogeneous list. And once again, I forgot language. And so we can do that using gadgets quite nicely. And let me write in the definition here and then we'll explain, go over how this is working. Oops, that's wrong. Okay, this won't compile because I'm missing a whole bunch of extensions, which I believe are these. And let's put that in just for good measure. Okay. So what on earth is going on here? So what we have here is we have this type H list, which is an indexed type. So just like a list would take the type of all of its elements, well, an H list, we can't give one thing that's going to be the type of all of its elements because we want this to be heterogeneous. We want this to be able to store ints and bools and cars in them. And so we have to give a whole list of types that will correspond to the actual data elements that are around at runtime. So just to demonstrate this, let's see, let's do that deriving show. Oh, never mind, let's not derive show. That's not going to work out at all. It would, but it would take a lot more effort to get that to work. So we're not going to do that. Instead, we are going to do this. We're just going to ask, is this well typed? And it will say yes. So we can add true and then, oh, maybe unit and then maybe a list of just some character and then we can put a string in. Of course, we have to terminate. And it says yes, that is well typed. And it's well typed as an H list indexed by the list of all of the types of the elements. And so what's going on here, so there's a couple of other extensions that are at play. And that is that we're using what are called promoted data types. So Haskell has all of these nice data types so we can have the list type, for example, and you can make lists. But with promoted data types, now we can actually use lists in types. And the way that it works is you just put a little tick mark before the thing and then now you can use it in the type. So this just sort of extends our syntax of types a little bit. And so here, when I'm saying H list and then tick with empty brackets, what I'm really saying is that this nil is a data constructor that creates an H list with a list of types of the elements is empty. That's because there are no elements. It's the empty list. When I do the cons operator here, I'm going to take something of this type H and then the tail, a heterogeneous list where each element in the list is described by the types in the list T. And then what do I get? I get a heterogeneous list that the first element in the type index is H followed by the list of the other types in the list, of elements in the list. Yes, yeah. And also a bunch of other things like just and nothing and true and false. Simple data types, but interestingly not gadgets themselves, you can do this with. That's data kinds. Type operators is there just for the colon. Because that's an operator used in a type. But to get the tick working is data kinds. Yes. Just say that one more time. Sure. Yes, yes. Yes, but you that wouldn't really make sense for a heterogeneous list because things of other other than kind star don't have values to sort of put in the term level list. So in this case we don't want to do that, but there's sort of only so far this trick goes. And so when I said earlier at the beginning of the talk that there's some things that are going to happen in 712. Right now sort of this promotion trick works only once. You can promote one level with in 712, if everything works the way it should, you'll be able to sort of promote as many times as you want essentially. If that comment went over your head, just don't worry about it. It's not really all that germane to the top. So we have this heterogeneous list. Other questions about the definition I've written up here? Yes. It works with sufficiently simple types. It can be one you made up yourself. And my experience is unless you've done a lot of Haskell type theory, it's hard for people to predict what sufficiently simple is. Maybe it's sufficiently simple. Bull is sufficiently simple. S tie is not. Okay. So we have this list. We'd like to be able to extract from it. So I'm going to write a get function and so we want some number and then we're going to take some h list, which has some index. And then, well, hard to know exactly what this is returned. So I'll just leave that blank for now. And then, so if the number is zero and I have some x cons onto x's, I just want to return x. And so I know there's a little bit more work to do around this, but let's try that. And I'm going to get this type error. And so this here, this is a type hole, which is a new feature in 7.10, which allows you to put underscores in types. And so to enable that, I need to say partial type signatures. If you don't have 7.10, don't worry about this too much. This is not a central part. But what it's saying is that I want to leave this part of the type out. GHC, you figured it out for me. And in the end, it can't figure it out for me. It can't know what this is. Because we don't know what the types are in ties. We don't know what this can return, what this is going to return. And we also don't know what number we've passed in as our int. And so that big heterogeneous list that we saw before, maybe this will return a bool, maybe it will return a string. It depends on what int we pass in. So there's just no way this is going to work out. So this is doomed to failure. So let's get rid of it. Instead, we need to make another data. And let me write this one in, and then I'll go over how it works. Oops, wrong one. So the LM type describes how a certain type can be in a type-level list of types. So ez, z here stands for zero, says that the element x is the first element in this list, which we can see because I've used x twice. ds, s stands for successor here, says that if x is an element of the list x's, then, well, x is an element of the list y consed onto x's, but of course it's going to be one element later. So that's successor there. And so we can build these up. So let's look at an example. I can say, for example, that if I have the type-level list bool int, oh, I didn't compile. Oh, I need to have the word LM. There we go. So what we have here is that I can say ez, and that shows me that bool is the first, or the zeroth element of this list. Or I could say ez, that's LM, same list, int. And so now this gives us enough power that we can actually index into our heterogeneous list. And so that will look like this. So we want to write a get function that uses this LM type to index in because now if I know that a certain type tie is in my list of ties, then I'll be able to look in my age list and then get the tie up. And so that is exercise two, is to write get, which you'll see. So I fully expect that a lot of you are sort of mystified about all of this right now, but it turns out that there's sort of only one way to do this and it's not so hard to just sort of follow along the garden path and you'll get there. And so let's see. Hopefully that's my next slide. Is it exercise? Yes, it is. There we are. Okay, so we'll do that now. I'm looking at the time and there's been a lot of great questions. It slowed things down a little bit, but I think that's been good. So if you feel the need for a break, now might be the good time to do that and then we'll go over the solution to get. If you want to try it, do that. And then I think we'll reconvene in about 10 minutes. But I think it will be, to everyone's advantage, to sort of overlay the break with exercise two. So here we go. I said at the beginning that you sort of follow your nose through it. And well, the first thing that we're going to do is pattern match on what we've got here. So we have an LM, so let's do a pattern match. And then the H list, well, if I want the zero with element, well, I'm going to have to pattern match on the H list as well. So it's going to look like that. And then let's just leave that out for now. Does the first part compile? Ooh, it does. That's good. And then here, well, if I have a successor, that means that my element is somewhere later in the list. So I'm going to pattern match on the H list, like that, and then recur. Okay, so it says that my pattern matches are non-exhaustive. But let's see what happens here. I don't even know what I do in that case. Ah, I get an error saying inaccessible code, right? We know if I have one of these LM objects, then by the way that LM is constructed, I know that the list can't be empty, right? Because both constructors for LM, both ez and es, lead me to a case where my list has at least that one element. And so I don't have to worry about the null case. So this get function is actually total, and I don't have to worry about sort of missing. So this is that bug? So yeah, so the reason that we got that warning is that same bug that should hopefully be fixed soon. But it took, you know, 12 very densely types at pages of mathematics to get there. I don't think it was, that by itself was a PhD. So, okay, so there's get. I'm going to transition from here to talk more about this Glamda interpreter. And this interpreter uses gadgets a lot. We're only going to be able to see a slice of it in this workshop. But as I was creating it, it sort of more and more features popped out of it that make it sort of a good case study for learning about gadgets. So my hope is to sort of spend more time over the summer actually writing a bunch of blog posts and tutorials about it because there's a lot of different things that are in there. Which I didn't realize at all when I started doing it, but it just sort of came together nicely. Anyway, let's take a look at what it is. So this is actually a package that's released on Hackage right now. Yes. Oh, yeah, sure. Oh, yeah. Yeah, I never think about running my programs. I just type check. Yeah, yeah, I don't... Sure, so I can say get easy on the list a true followed by hello, followed by just five, which is an int. Let's not play around with overloaded numbers. And that will get true. Or I can do that and it gets hello. Yeah, it actually does work when you run it. Okay. So, whoops. Okay, so here's the Glamda interpreter. It is a basic simply typed lambda calculus interpreter, but hopefully people are somewhat familiar with the idea of lambda calculus if this is lambda conf. But I'll show a few examples. So what it can do, very basic things, right? It can add, it can do conditionals. We can write a function and it will do that. And so just to explain what's going on here, so I've defined this lambda function here and I'm applying it to the number two. The other thing that we see here is I have given a type to my x. This does not do type inference. So every time you have a lambda you have to label it explicitly. And I can also define constants. So here this is a reverse application. So I'll explain a little bit more about what that output is in a minute. But here I can define reverse application. I can define the plus one function, which looks like that, and then sure enough, all of these things work together. So it sort of gets you off the ground. I guess you can do a little bit with it. Just to show that it's not totally brain dead, my test for language is can I figure out prime numbers? And this can figure out prime numbers. Knows that that is true, but that one's false. And so another feature of it that will come into play a little bit later is that we can actually see this happen step by step. So I can step rev-app five plus one, and it shows us this thing reducing at every step, which is pretty cool. And of course it can tell us the type of things with colon type. Or we could just do colon t, five, that's it. You can do that step thing with the is prime also, but that's a lot of steps. Okay, so this thing, it's working. The one thing I'd like to draw everyone's attention to is what I'm doing with variable binding here. I don't know if anyone has looked at it. How many people in here have heard of de Brown indices? Oh, wow, that's like a whole ton more than I expected. So the idea is that when I write a function, so we'll see, oh, it's already scrolled off. When I wrote my rev-app function, I labeled the different parameters, or the different, yeah, parameters x and y. But the choice of x and y is totally incidental there. And in the implementation of an interpreter, it's really annoying to have variable names. Because maybe somewhere else, there's some other function that talks about some other variable x. And in the course of evaluation, maybe I'm going to do some substitutions, and these x's collide, and then I have to go and rename one of them to x1. We see this all the time in GHC error messages when our type variables change from A to A0 and things like that to avoid collision. And so what I've done here is I've used this convention called de Brown indices, which instead of storing the name, I just store sort of how many lambdas I have to jump over to get to that variable. And so here in the definition of rev-app, we see that y has become number zero, because I don't jump over any lambdas to get to the y, because it's the one that's right here. The x, to get to the x, I have to jump over one lambda. So it's number one. Yes? How did you get the code? I've used de Brown notation before, and it's always this massive pain, like trying to remember where your parentheses are. It's just a little logic in the pretty printer. Very, very impressive. That was another thing that came out of this, and I was like, ooh, this is a great way to learn about de Brown indices. Yeah, it's actually a tiny little bit of magic. I mean, it's remarkably straightforward to get that to work when you try to. So just to sort of drive this point home, I have one example here that I'll type in. What this does is not important at all, but I wanted to show an example of when you have a variable that's used sort of both within a binder and outside of a binder. And so here, this x, it's bound out here, but then it's used inside of this function that's abstracted over y, and then again outside of it. So in the output here, we see that here, to talk about x, I have to jump over one lambda, and here I don't have to jump over any lambdas, because this other one over here is sort of internal. You don't really jump over that one. But we can see in the colors sort of how this all lines up. Okay, so that's the lambda interpreter. Yes? No, they're the same x. They're the same x, but depending on the context, the de Brown indices are different. So de Brown indices are terrible for humans, great for computers. And so as humans, ignore the numbers and just look at the colors. Okay, other questions? Okay, so let's look at the code behind all of this. So in particular, I want to look at... Wait a second. I want to not forget to do an important thing, which I did forget, and that's to change branches. Great. Okay. Okay, so this is the expression type within lambda, and what's interesting about this expression type... Oh, there's elm again, by the way. That's one of the reasons that we worked in elm before, because we have to use it in here. Relates, as you might imagine, quite closely to these de Brown indices. So the idea within lambda is that after... You take that text and we have to lex it and parse it and process it a little bit, but once we've type checked it, I didn't show you an example of something that was type incorrect, but there is a type checker. If you type in something wrong, it will tell you so. Then it will eventually produce this X type, and the X type, through the magic of gadgets, can only store well-typed expressions. There is no way of building, you know, the expression of one applied to two here, right? So just to sort of drive that home, let me switch back. If I run glam, if I type one, two, of course that's no good, right? I can't apply one as a function. Well, this X type that I have, it won't even allow me to create such a thing. And so, by virtue of that, I know that my type checker has to be correct that I've written in this language, in this interpreter. There's no way I could have made a mistake in the type checker, because I couldn't create an ill-typed expression in this expression language. Let's sort of plumb through how that all works. Oops, don't do that. Oh no, never mind. Forget all of this stuff. Okay, we'll use highlighting instead of spacing. So we'll start with the easy constructors, int e and boole, so these are my literals. And what they're saying is, so int e takes an int, and then it gives me back an Xctx int. So X takes two different type indices. The first one is a context, which is the list of the types of all of the bound variables in this expression. So at any given point, as I'm looking at an expression, there's a bunch of lambdas that I may have seen, maybe none. And I need to know the types of all the variables bound in those lambdas. And so that is this CTX. So this is just a list of types, just like we saw in each list. And you'll see up here I'm using a sort of a different way of writing the header here, and I'm using what's called a kind signature. And star is the way that we write the kind of types in Haskell. So we could just think of int is a star and maybe boole is a star, but maybe by itself is a star arrow star, because it's not really a type. It needs another type to become one. And so here the first index to x is a list of types, and then the second one is a type. So here, I've said in any context, an integer literal is an int. Great. For the boole constructor, in any context, a boolean literal is a boole. The next one I want to look at is app. So this is for function application. So we're going to take two expressions and apply one to another. The first expression has to be valid in some context CTX. I don't care too much about it. And it's going to have type arg arrow res. It's going to be some function from some argument type to some result type. The next expression also has to be well typed in that same context, but it has type arg, the argument type. And then, of course, what's the result of this? Well, it's going to be an expression that's well typed in that same context of type res. If any of these pieces don't line up, then I won't be able to use the app constructor. So every time I use the app constructor, it's checking to make sure that, indeed, I have a function that I can apply to this argument. Let's look at lamb for a sec. So this is the kind of expression. This is a lambda expression. The body of the lambda is some expression, but that expression, it's not in the same context. It has one other variable available to it of some type arg. So I extend the context by conzing arg onto CTX here. And so I take whatever my outer context is and I add one more type to it called arg. And then, of course, that gives me an expression that's well typed in the context of type arg arrow res. So this is how sort of the functions are created. In the variable case, well, I'm going to pick out what variable I want, which is going to be one of these types that are in my context. So I use this elm to sort of say, to indicate which type it is in my context. And of course, elm is essentially a natural number, right? It's zero or some sequence of successors on that. And so, getting scrolling to work in Emacs is always interesting. So we can convert one of these into an int. So actually, this elm to int is used when I'm printing out those to Brown indices in the output of the program. And so it's saying that if I have the right elm construct to pick out a type tie for my context, well, then that's a well typed expression in that context of that type. The last pieces here are, we can have an arithmetic expression. So this is plus or minus or an equality comparison perhaps. And so here I know that in this language, all of my arithmetic operators operate over ints. I don't have and or or. So here I take some int, some arithmetic expression that's going to give me a result of type tie, another int, and then it gives me my result. And then we can see for conditionals, I take some bool, two things of the same type tie, and then I get a final result of type tie. And so if there's any mismatch anywhere, I won't be able to construct one of these things. And so that's sort of the key insight here is that I can use gadgets to do this. And the way that that's useful is, say, in writing the evaluator, because I know my evaluator has to be well typed. As I produce, go from an expression down to a value, then it's got to work out. So here I'll let you know now that exercise three is writing the evaluator, which, again, follow your nose. You can't really do it wrong. Well, at least if it type checks you can't do it wrong. So here what you're going to be writing is a function from that X type up here to this val type. So values are always top level. They don't have a context, so we don't have to worry about contexts here. There's three kinds of values. You can either have an int, which is, of course, of type int over here. You can have a boolean, which is of type bool. Or you can end up with some lambda expression that isn't yet applied, so we can't evaluate it any further. So a lambda expression is some expression. And then with one argument of type arg produces a type res, and then that's going to give you something of type arg-r-res. So exercise three is in the eval file. And we'll see here that here's this eval function, which you have to write. So all the instructions are, again, linked to from the usual GitHub page. So let's do that, and that sort of brings us to the end. I have a few closing remarks, but we'll maybe have, I don't know, 10 minutes or so to do eval. Let me not quite get there, but see if you can get maybe the first couple of constructors. I'd like to wrap up pretty quickly. The answer to exercise three is up here. Was anyone able to get this? No. Okay, that doesn't surprise me. There really wasn't as much time as I wanted to have there. So I'm sorry if anyone was sort of struggling against the wall on that uncomfortably. But sort of each individual piece, again, there's just, you just sort of follow the types. And this sort of the final point I have, whoops, is about why I like gadgets. And it's because of this. It's because when I'm writing my evaluator, I know once a type checks that it's got to work. And it turns out that the evaluator is not at all the hardest part of this. So the brown indices are fiddly, right? This is the algorithm that has to be used when you substitute one expression inside of another. And in fact, when I tried to do this without gadgets, I just, I didn't even sort of know how to do it. It's not that complicated. But you look at this and you really don't want to implement that, right? That's not a pleasant experience. But with the gadgets, there really wasn't a way I could go wrong. I didn't look that up in a book before implementing it. I just sort of typed until it type checked. And it worked out. And I knew it was going to work out because my type works. So in much the same way that when you do typed programming, you sort of have this, when it actually compiles, you have some confidence that it might actually work. Well, with more types, you have more confidence. So that's sort of my end takeaway. The last thing here is just talking a little bit about how we're doing some compile time reasoning about something that's going to happen at runtime, right? That people have their own, their own functions that they're going to write with their own types. You don't know what they are, and yet we're doing some reasoning about them. And it all comes back to the fact that we can use sort of higher rank types. Sort of want to give people a little bit of time between sessions here. But here, this is the type of the actual type checker that takes a UX, which is a not yet type checked expression, and type checks it. And here, my sort of the thing that processes the checked expression has to work for any type. And so that's how we can sort of reason about these things at compile time, even though they won't exist until runtime. And so I encourage you to look at the check module of this if you want to see how that happens. But a lot of the examples that use GATFs to do this sort of thing, sort of assume that you just sort of sit there and type it, like my H list examples. I was just sort of typing there in the terminal. The second type check everything is all obvious. But here, actually, we can type check something and then run it later. And so that actually does work. So here's some further reading. Thanks for sticking with me. And I hope this was helpful to everyone. And enjoy the rest of the conference.