 Hi, and welcome to my talk. My name is Maddie, and my partner in Crimes Against a Type Checker is Augustine Mista, and we're both PhD students at the Thomas University of Technology in Gothenburg, Sweden. Today, I'm going to talk to you about the Dynamic Haskell plugin for GHC. The dynamic here refers to dynamic types. You might know dynamic types already from top hits such as Python, JavaScript, and Erlang, and what dynamic types essentially allow you to do is to delegate type judgments to the runtime, which means that you don't actually have to decide what you're going to do until you have a value of that type. Now, this can be very nice. For instance, for do fast prototyping, it can be very nice to do heterogeneous list, and it can be very nice, especially in cases like Erlang where you're not sure what the data you're going to be getting is looking like. So if you're running a node or a server and you get a message, you might think you know what the type of that message is, but you want to be able to safely work with it until you actually have to make a decision. And dynamic types can really help you in that respect. So you can use dynamic types in Haskell already by using the data.dynamic module. It's a very simple module. It has this dynamic type, which is an index type rep and a value, which together form a dynamic value, which contains some type rep, which is kind of a representation of the type and the value itself. It has a two-dent function, which takes in a value and it returns a dynamic. But with the caveat that you have to be able to give it a representable type of value to goes into this type rep. And then you have the from dynamic function, which is kind of a safe interface. And it's very cleverly done. It essentially says that if you give it a dynamic and you want the type that it actually contains, then it's just that value. But if you want a value, if you give it a dynamic value and it has a different type than from the one you expect, you return nothing. So how do we use this? Well, here's a message passing example, right? So what we have to do to use dynamic is we have to apply two-din to every element in the list, right? And then when you handle the message, you have to use this just dance, right? You have to say, oh, you know, if you give me something and it's a Boolean, then we handle it as a Boolean. If you give me something as an int, we handle it as an int. If you give me a unit, we handle it as a unit. Otherwise, we're going to throw in error. And you notice that we are giving this a string. So it's going to crash if we give it a string, right? We don't know what happens if we get a string. It's a very common case in dynamic languages, right? That if you give something that is expecting a certain set of type, something that's not in that set, then you just crash. So if we run this code, we get the expected output, right? We get yes, we get 42, we get the no, we get the unit. But yeah, when we hit the string, we just get under. And this works, right? But, you know, we're Haskell, right? We're at this point in the Lambda Cube where we have a very strong type system. The strongest possible type system while we can still deterministically infer the types of all the things, right? That's kind of the point where you want to be at in the Lambda Cube. Of course, this has some caveats now, right? I mean, there are things that we can't actually type deterministically in because we've added stuff to the type system, right? So we sometimes have to provide annotations. And that is expected. You can see with like the 42 here, right? We have to tell it if it's an integer. Otherwise, it's just going to be a mess, right? But the idea here is, you know, we have, we know that we have a value, value Boolean, right? And we know that we're expecting a dynamic. So why don't just do the obvious thing? Why don't we just turn that Boolean into a dynamic? You know, when we look at all the syntax that's just extra here, we can just remove all these two things, right? Just, just coerce it into a dynamic. Just making it a dynamic, right? Remove all this from dynamic maybe dance, you know? Just say if it's an X Boolean, then I want to do this. If it's an int, I want to do this, right? Now we've defined this sugar interface to kind of how we want it to look, right? So, you know, you give it a list of things, right? And it's expecting a list of dynamics, but it's not a list of dynamics. It's something else. Then we simply add a call to tune in everywhere, right? That's just how, what we should do. It's kind of one obvious thing to do, right? So we have this already in the plugin, right? Now, this is function here. It's just a pattern synonym, actually. It just removes the just dance, right? It just says, if it's a Boolean, do this. If it's an int, do this. If it's a unit, do this. And now we're not doing anything extra, right? We don't, we're just communicating our intention to the compiler without all that extra overhead, right? So this works with just the patterns synonym and view patterns, right? But for this, we need some sugar, right? We need this to mean something else, right? We need this to mean tune in true and tune in 42. But that's okay. We can do that. But this is not, you know, this is not kind of the endpoint, right? You know, we want to be able to not just match on the type, right? We want to be able to match on the constructors themselves. And that's what you can do in Erlang, right? You get a message and what Erlang usually, in Erlang, you usually match on the, both the type and the pattern of the message that you get. So by being able to do this in Haskell, we're very close to what Erlang does and they do it very nicely. And they're a hugely successful language. So I think this will be very nice. But another caveat is that in Haskell, we have a lot of type classes, right? A lot of the stuff we do is using type classes. So being able to make things dynamic doesn't really help if everything you, you're going to apply it to a type class and then it doesn't work anymore, right? So here's one such example, right? We have a type class and it has instances for pool int and unit, right? But there's no dynamic. There's no instance for message dynamic. So if you call this message function on a list of dynamics, it will just complain that there's no instance for message dynamic. So we would like to be able to do something there as well, you know, kind of being able to work with type classes. Okay, so what's the trick here? Well, the trick is actually using type checker plugins and this message, right? That that Booleans is not dynamic, right? Or that there's no instance of message dynamic. So the trick is writing a GHC plugin. So we were already working on the GHC plugin for weak runtime irrelevant typing for security. So there's a nice library in Haskell called the Mac library. It was written by Alejandro Russo. And what it allows you to do is essentially label boxes with security values. So you can label the box as either private or public and using this, you can kind of ensure that you only use private values in private context. So you're not leaking private values and you can actually be sure that you're not accidentally using a private value. You can of course use private values if you need to, but then you kind of need to state that in the type system. But we wanted to be able to run potentially insecure code, right? Or code with all these security types and just being able to test it without having to bother with all the security. So we wanted to be able to go from a secure type to an insecure type. I was really nice about the Mac library because it's all new types and it's all phantom type variables. So we can just use safe coerces. So you can safely coerce from a whatever type to a secure version of that type and you can safely coerce back to whatever type you want, right? If it's the same type as the one wrapped by the label box. So we wanted to be able to weaken the security constraints, right? But this of course creates an error, right? In the type system, right? We have two ways of typing things. So we've gone the theoretical way of saying we want to type it the way that produces fewer warnings. This is not very clear yet. We're still working on how to kind of theorize this correctly. But, you know, you want to be able to type things only if they didn't work out regularly, right? So that's exactly what we do. We catch this int is equal to dynamic message which GAC has tried as much as possible to solve and we can solve that, right? But we know that everything that can be solved has already been solved by GAC. So we only kind of match if it would not compile otherwise. So it's a, but then we've put in an order requirement on the type system, which is also, which is not theoretically nice. So there's some work to do there. Okay, so how do we do it? Well, it's based on these three functions that we insert into corp, right? Let's just go over these functions, right? So we have the dincast function. Now this one goes from dynamic to an A, right? And you kind of, whenever you're stuck, you want a value of A and you only have a dynamic. What do you do? Well, you can go from dynamic to that value, but from dynamic goes to an maybe, right? And we just want the value. So how do we do that? Well, we do the type, the dynamically typed thing where if you give me something and it's not of the type that I expect, then I just crash with a message saying, you know, you gave me an int, I was expecting a string. This is very classic in languages like Python, right? So, you know, this direction is very simple. You have something and you want a dynamic and this is just a two-dimensional function from data dynamic, right? We just wrap it in dynamic. Oh, it works out very nicely. This is the final function. This is kind of a cool function. And this one does the dynamic dispatch. So what happens is that we get these, you know, int is equal to dynamic function in the plugin, right? And what we do is that we commit a crime, right? We lie to the type check, we say, in general, ints are dynamic. That's fine. But what we do later is that we do a core pass where we fix all those lies and we say, oh, I told you int was dynamic just by coercing, but that's not actually true. So we place this coercion with a to-din, right? Go to the dynamic function. But to do type classes, that's not enough. You can't just go to the type class. So what we actually have to do is that we have to generate this dispatch table. So what we essentially do is that if you want to generate the message dynamic, if you want to call message on dynamic, we generate a table where all of these type parts will be the string or will be the, not the string, will be the bool and will be the int and will be the unit. And then this dynamic value here is actually the function at that type. So then you send it to the right class instance. So it kind of works like dynamic dispatch. And this is exactly like dynamic dispatch works in some languages. It's a very nice way to do it, right? And we automatically generate this table in core when you hit this. So it's a big core function, but it works, it's very nice. So these are the three functions that we kind of base it on. And of course, so we have it working and we get it to compile and it doesn't generate lint errors. But of course, some of the errors are very confusing if it generates a lint error, right? So for instance, if you don't specify that the 42 has to be an int, then it just crashes with a long error saying essentially that, you know, it couldn't figure out the type of int or integer, but that's not the error, right? Because we already solved the problem. We solved the type error. So we don't get the ambiguous type variable error. So with some work, we need to do interface better with the type system. Let's show you a demo of how it works. So here we have the message module from before. I mean, we're using some type extensions and we enable the plugin, right? And what we get is that, I mean, this is all integrated into GDC. It's all integrated into the excellent work by the Haskell language server team, right? So now it actually marshals, that's what we call a dynamic cast, all of these values into dynamics. And it says, okay, this bool here, I'm gonna make it into dynamic, right? I'm gonna marshal this into dynamic, this bool. I'm gonna marshal this unit and I'm gonna marshal this string to a dynamic. Now this already works, right? So if we compile and run this module, then, you know, we get all these warnings, but that's fine, we can still compile it and we actually run it and we get the expected error, right? We hit this unrecognized message clause. And if you turn off the plugin, then it simply turns these errors into warnings, right? Couldn't match dynamic with actual typo, which is what you expect, I mean, yeah. So this works, that's great. So here's the other module we were showing you. So here we have the same, right? We marshal all these things, but now it's not enough. So the pattern is not enough. So we actually have to generate the dispatch table. So we build the dispatch table, we use all the instances that we have in this module and it actually uses the instance lookup functionality to find all these instances and we just generate the dispatch table. And then when we run this, we get the expected value. And what I also wanted to show you here is how nicely it interfaces with the, oh yeah, sorry. So I already turned off the module. So now I turned it on. We get the expected message, right? No instance for message dynamic, but then I turn on the module again and then we get a marshaling messages and then builds the dispatch table. And then actually works. It just calls a function of the right type. But of course, we call it for a string. We don't have an instance for string. So then we get that classic error here. No instance of message, list of charts when dispatching message, right? Which is a, you know, expected error. You get a runtime error if you try to do the wrong thing. Okay, so it works. And I think it's very nice, but we're still kind of in the process of molding it and seeing where we can take it. It's very early proof of concept, right? We're not done. There's a lot of theory to be done. There's a lot of work on, did we doing like, what can we do now? If we have this, like are there things that we would like to be able to write in Haskell now that are much nicer to do in this way. There's a lot of theory that it's called gradual typing, which is this thing where you have, you have untyped code and you have typed code and then you're using parts of the untyped code and the typed code and parts of the typed code and the untyped code and this mishmash there, the interface and the boundaries where you have partially typed code. What happens there, right? And there's a lot of body of theory called gradual typing around that, which we have to explore and the connection between them. And we got very good comments from the reviewers already. So thank you for that. And, you know, we're really here to present this work in progress, to get feedback. And hopefully if it works out and the plugin shows success, and then there's a nice thing, maybe we can make an extension something, make that'll be nice. But we're really doing a, you know, try first, see your eyes later. We try, see what happens, see how it works, try and figure out what we want to do because our motivation here is just do the obvious thing. What is the obvious thing to do? Like what would you expect to happen with dynamic types? Which is what Python and these languages do. They just do what you would expect to happen. But you, yeah. All right, that's all I have to say for you today. Thanks for coming to the talk. And if you have any questions, we'll be here around to answer your questions. And if you watching this later, then you can reach us on Twitter as well. So I'm Trit Lo and Augustin is Augustin Mista. All right, thanks a lot and goodbye.