 Thanks everyone for joining us. We have with us today, Anupam Jain. He is going to be talking to us type reification with PureScript. So we're looking forward to hearing about that. Thanks. Hi, everyone. Welcome to this talk. Let me just share my screen. And we'll get started. So yeah, so the title of my talk today is. It's pure script typeable, which is a library that I wrote for PureScript, which basically provides type reification. And I'm going to explain what that means throughout this talk. But if you wanted to check out this library, you can check it out on my GitHub, which is AJ NSID. And you'll find a library with this name. So what is, you know, typeable typeable is a type class. And I'm going to be talking about that. But why do we need it? What does it do? So I'm going to start with a motivating use case of a very real world problem that people might encounter. And how does typeable help us solve that, right? So let's say you're building a database for a company. And it would look something similar to this, right? You would define all your data types, each data type representing a particular aspect of your data model. And these are, you know, very nested data types. So a company has multiple departments, a department has multiple subunits and so on, all the way until you have information about a person itself. And the person will have salary, name, address, those kind of things, right? Things that you might actually want to perform some operations on. So this is a very common situation that you're in that you have this very deeply nested data structure. And you want to perform some operation on a leaf of that data structure. So in this case, we want to do something like this. We want to give everyone in the company a raise and we want to define a function called increase, which takes a salary modification function, which is this salary to salary function. And it basically takes in a company and returns a new company, which has all these salaries modified according to the salary modification function. So how would you write that? This is really straightforward. This is basic Haskell programming. But this is what it would look like. You would define for each level in that data structure, you would define a function that would do this modification for you. And if you look at this, it's like a bunch of definitions. But if you look at everything inside this red boundary here, you would see that all it's really doing is that it's going into that data structure and applying this modification function K recursively. So let's look at this. So increasing the salary across company basically means that we apply this function, which is a department modification function on all the departments in the company. So it's just a map. Then IncD is a department modification function. It takes, again, a salary modification function and it returns a department modification function. And again, we basically go inside the department data structure and we apply IncE, which is an employee modification function, and IncU, which is a subunit modification function. And we basically apply that. So we basically keep on going, keep on drilling down into the data structure until we reach what is the meat of this entire operation, which is actually modifying the salary, which is right here outside the red boundary. So let's focus on that for a little bit. So this is what that function looks like. And it's just identity because once you have a salary modification function, you know how to modify salaries. So this is the meat, the actual logic of what we wanted to do. We wanted to change a salary when we already have a salary change function. But instead of writing the simple bit of code, we had to write all this rigmarole around how to modify salaries. If we were actually doing this in an untyped, dynamically typed language, it would be very easy. We would have something like this. We would have something like this. But here we could just check if the value that we were trying to modify is a salary. If it is a salary, then apply the function. Otherwise don't do anything. And that would have been very easy. But because we want to do this in a typed safe way, we want something like this. We want to define a typed function that basically can take any type A and a salary modification function. And it gives us that type modification function. And this is what pure script type will allow us to do effectively. So this is a motivating use case. This is one of the use cases that pure script type will allow us. So let's look at some ways in which we could accomplish that. Given the problem statement where you have a class of types and you want to do something specific for each type, you might automatically reach out for type classes. So you might define a type class called map salary, where you have an increased salary function, which basically takes a salary modification function, and then it returns that type modification function. So before I continue on, there's going to be a lot of code on these slides. And if you have any questions, you can put them in the Q&A. And I would, if I think something needs to be answered immediately, I would answer it immediately. But otherwise I'll continue on. So you would define a class like this. And then you would define an instance of that class for every part of that data type. So let's say we wanted to define it for an employee, you would define it something like this. And it's a fairly straightforward implementation. You would call that function the salary modification function F. You would call that salary modification function F on every subpart of that data type. So you would call it on P, which is a person. So you would change that person's salary. And then you would call it on every, on the salary itself. And P is the personal data of that. And here we would also like to be able to handle cases where we don't have any salary information. So we also have a generic fallback instance where for any data type, which we haven't specifically defined an instance for, if you just pass that, then the salary modification function does not do anything. It keeps the data type as is. So this is one way of solving this problem. And it will solve the problem in a very specific use case where you want to just call map salary increase directly. If you wanted to call increase on a particular data structure directly, and the compiler has all information about what that data type is, then it can go through this instance chain and figure out what to do or how to do it. But there are a bunch of problems with it also. And the problem is that we had to define an entire class hierarchy for that specific function. We had to define an increase salary function. And we had to define a specific class that only applies to that increase salary. So it's a lot of work just to avoid some work. So this is just as much work if not more. And we wouldn't like to have that much boilerplate. What we would like to do is have a general solution where the traversal through the data type is separated from the actual operation we need to perform on that data type. And we're going to look at how we can do that. So this is the kind of thing that we would like to do. We would like to have a function which takes a salary modification function, checks if the value that you were provided is of type salary. And this is not Haskell syntax. You can just check for types like this in Haskell, because all types must be known at compile time. And then, but let's say we were able to do that. And here salary represents the type salary. So if we were able to do this, that if A is an instance of salary, then just apply the function. Otherwise return the function as is. So let's try to write this. And here is what the signature of that function, instance of, would look like. It would take any type A and it would take something that represents the type of that A or some other type. And it would return a Boolean, which basically tells us if that value A is of this particular type. And as soon as we write this, we know that we need something, some value that represents a particular type. To be able to perform this logical operation, which compares the types of these two things, we need to represent types at the value level in some way. So what is that? So that's what we want to solve. And the standard solution for this in both pure script and Haskell is something called a proxy. Where proxy is basically a value that has a phantom type parameter, which represents a type. So instead of, so if you wanted to provide type information to a function, you can pass an a proxy instead. And this usually works, but it doesn't work for our case because proxy doesn't have any information at the time value level. So at the value level, it has only one data constructor called proxy, doesn't have any arguments. And all the type information is at the type parameter level with a phantom type. So if we wanted to actually start writing this, we would be stuck immediately because we know that it's a proxy, but there's no way to actually get the type. And we need to be able to get the type to be able to compare it with the type of it. The proxy doesn't suffice for us. We need something better. And in pure script, pure script is a language that compiles to JavaScript and it has very good FFI with JavaScript. So a standard solution that I tend to jump to is just punted to F5, punted to JavaScript. So you can define, so we don't know what the type representation is going to be, but let's say that JavaScript, the untyped language has that representation and it's able to somehow represent the types of every type in the type system and we are able to do a bunch of operations on it. So the way to do that in pure script is that you define a foreign data declaration. So we just say that there is some foreign data called type rep. We don't know what it looks like, but we can just access it directly. So we define something called type rep and then we need to be able to create values of that type rep. So we, again, in a foreign function interface definition, we define a function called type rep which takes a proxy, which here represents the type that we want to create and it returns a type rep for that type. Now the proxy A part of it is basically just to let the type system know what type we want. So it's not carrying any value at the type level, at the value level, but it's just for the type system. So we have a type rep which basically gives us a type rep for any type. So an example would be defining a functional Boolean type, which gives us a type rep that represents the type of Booleans and we would create it like this. We would pass it a proxy that is of type Boolean. This here is pure script specific syntax where, and I think this would be valid in Haskell also, where we basically want a proxy and the type of this proxy is proxy Boolean, right? But because this data constructor already tells us that it's a proxy, we just don't know what type of that proxy was. So we can omit the proxy type constructor here and we can just say that there's something of type Boolean. So this makes it slightly more concise and more pleasant to write. So to me it reads like it's a proxy of type Boolean, right? But actually it's a proxy of type proxy Boolean, right? So yeah, so this is how we can define a Boolean type rep by calling the type rep function. And then we want to write instance of, right? So we, one way to do it is to have, is to basically create a function called equal type rep, which can compares to type reps and that returns a Boolean, right? So instead of taking a type and a value and then comparing the types equal type rep basically compares to type reps and it tells us whether they're equal or not, right? So here to check if some value is an instance of some type, let's say Boolean type, we basically get the type rep for Boolean, right? And we compare it with the type rep of that type. So here this is how we are doing it. We basically are given a value A and a type rep T and we want to check if the type rep T, the type of value A is the same as the type rep T and we do that by first extracting the type rep of that value A. So we get a type representation for that value A and then we compare it using equal type rep. So this way, if we already had these functions defined for us and this opaque data type, we are able to come up with an instance of function like this. So this could be our API surface, right? It's a very generic API surface, which has just three simple things. It has a data type declaration that's opaque to us. The users cannot create new values of this type rep, except by calling the type rep function where we specify the type that we want using proxy and then it gives us a type rep, right? And the type reps that we get have this property that if the type of A and B are the same, then the type reps of A and type rep of B also have to be exactly the same. So if we have a type rep true and type rep false, they both should give a type rep representing booleans and it should satisfy equality, right? So we should be able to just compare those two type reps and get through. So that is important. And we don't use the Haskell equal operation for this. We use equal type rep for this. So the equality is also an FFI function, which we use here, right? So it can compare two type reps. And then as I said, we can define instance of as a separate utility function, which depends on EQ type rep and the type rep function. So this API looks pretty good if you can implement it in FFI and we can. So I'm going to go through the FFI bits of it at the end, but this is one way of doing it. But if you wanted to do it manually in pure script or Haskell itself, then we would do something like this, right? This is one way of doing it directly in pure script where you basically create a data structure which enumerates all the types that are available to us. Right? So you can define a data type rep, which has a constructor for integers, a constructor for characters, a constructor for array. And now we need to also know the type of the elements of the array. So we have a type rep as an argument. Then a function takes two type reps because the argument and the result types also have to be specified. And similarly, tuple takes two type reps and so on. So we can enumerate all the types we have in the system, right? And then we can write an EQ type rep function, which basically does a direct comparison. So if it's tnt and tnt, if you're comparing two tnts together, they're obviously the same so it can return true, right? So there are some limitations of this approach, which we'll get into. But this is one way of doing it. You could actually implement this yourself. And then you want to create type reps. So with this manual data type approach, one way of creating type reps is you create a typeable class, right? And then you can define instances for each data type that you support. So you create a typeable class which has a type rep function. And then you define an instance for integers. So type rep when called on integers will give me a tn and so on. You have to define an entire chain of these. This else thing might be confusing for people who are coming from Haskell and don't, are not familiar with PureScript. PureScript has a feature called instance chains, where you can basically provide a bunch of instance declarations to the compiler and provide an order in which they should be checked, right? So you can define something like typeable int else typeable a. So for any type, it can check if, if it's an integer, it'll call the first, and it'll use the first instance. Otherwise it'll use the second instance. So this is a very useful feature for resolving instances. Right? So given that you can easily create the increase function as we wanted. You just check if a is an instance of type rep of salary, we want to check the salary, then do f a, otherwise just do a. And this will be a solution, right? Not quite because we immediately run into a problem. At this point, we have two branches, right? The first branch checks if the type of a is salary. But in the body of that branch, the compiler still doesn't know that. The compiler still thinks that a is some opaque type that it doesn't know anything about. And it doesn't know that it's salary, right? So this particular check is, needs to be propagated to the compiler somehow, right? We know it and one way of fixing this and not really fixing it, but when we're working around it would be to just unsafe coercive because we know that they are the same. So you can easily define a course to salary function, which can take any value and unsafe coercive to salary and then use that here, right? And this is obviously unsafe, but it's not any less safe than what you would do in a dynamically typed language, right? But this is not good enough for us. We want it to be safe, right? And that's one thing that type bill allows us to do. So we're going to continue down the path of our manual type representation and see how we can change that. So what we really want to do is, at this point when we do the instance of function call, instead of returning a Boolean, we want to return something that allows us to convert type A into a salary, right? So instance of will only return true if A is the same as salary. So it only makes sense that when you do instance of, it also returns as a function that allows us to do that conversion, right? And let's call that a coercion, right? And this is known as a type witness. It basically says that you have performed a value level operation which got the value of a type. And you know that this type is a salary or a Boolean or whatever. Now you also want to type witness that can tell the compiler that this thing is true, right? So in this case, our type witness is a conversion function which converts A to salary. And if we had such a conversion function, we could write it like this then, right? We can say that if instance of returns a value, a conversion function, and we indicate that by wrapping it in maybe so it can fail also if A is not a type of the type salary. So if it returns a just, then we also get a conversion function called coercion and then we can just call F by first core second, right? But if it returns nothing, which basically means that A was not of type salary, then we just return A as is, right? So this would solve the problem. But again, we start writing instance of and we immediately run into a problem. Instance of is supposed to return a function that converts A into salary. But the arguments to instance of, they don't have salary anywhere, right? So instance of should have a type, it should take a value A. It should take a type rep and the type rep here is entirely opaque. It doesn't say that it's the type rep for salary. We want to convert it to a salary, right? So it just says that there's some type rep. And if the type reps match, then it wants to convert it into the value represented by this type rep, right? Into the type represented by this type rep. But it doesn't know what that is. So there's no way to actually write this function and have a type check. We can't just come up with a type out of 10 error, right? Which matches this type rep. So there's no way of actually writing this. To solve this, we do something which is known as index type reps, right? So until now what we were discussing was unindexed type reps where the type rep is just an opaque type without any parameters. But to solve this problem, to be able to carry the information of the type with the type rep at a type level, you basically add a phantom type parameter. Oh, it's not really a phantom, not necessarily because it's completely opaque. So we don't know what it is. And we add a type parameter A, which represents the type that the type represents, right? So the type is now tagged with the type itself. And the API remains the same except now we have these types coming along with us. So let's look at instance off. Instance off will now have two type variables, A and B. It takes a value A. It takes a type rep of type B, and then it returns a coercion function where A, and we represent this coercion with a tilde. So it returns a coercion function which converts A to B, right? And ideally it should also return a function that converts B to A. If A and B are the same type, then we can go either ways, right? So let's represent, for now we let's represent that with a tilde. And again, this comes from EQ type rep, which represents that type coercion, right? So this is why we need index type reps rather than just normal type reps. And we haven't lost the unindexed representation because you can always use existentials for this. So type rep has a type parameter, but if you just wrap it inside an existential, you can get an opaque type parameter type which does not have a type parameter, right? And this is how you would do existentials in PureScript. PureScript does not have existentials built in, and this is how you work around them, right? So a lot of this talk is about how you work around some things in PureScript. And this is a library called PureScript Exist, which basically defines these definitions. And this is how you would do it. So it's just unsafe coercing it. It just forgets, right? It forgets what type it has. And then, so make exist basically just the unsafe coerces, a type representation here, F would be type rep. So it just converts a type rep A into a sum type rep without a type parameter A. And then when we want to get it back, we still want to make it type safe, right? So we have this higher order type here, which it's higher order because it has a for all in the type. So what we get is, so run exists as a function that can take, you can think of it as a callback, right? So it's a callback that can handle any type A, right? So it will basically be given a type A and it can handle any type A, right? And it can give a value R and then we can return a value R. And because this is a higher order thing, it's the for all is inside the type of the callback. It's not possible to misuse this. You can't actually leak out the A from the callback. So you can't know outside this callback what the type is, but you just know that it's some type, right? That you can handle. So this makes a type safe and this is how you do existentials. And you can recover the unindexed representation using this existential, right? And then this is what the API would look like for the indexed API where type rep represents a type rep A. And here you will notice that we have gotten rid of the proxy argument. We don't need it because the way you're using type rep, just by the type of it, like here, we wanted a type rep of type Boolean. So we will just say type rep is a type rep Boolean, right? Because now the Boolean is a parameter to type rep. So you can just say that I expect the type rep to have a type Boolean and you don't need to pass anything to it. And EQ type rep now takes two type, type reps, A and B, potentially different types, but maybe the same. And we don't know if A and B are the same type and it returns a coercion wrapped inside a maybe, right? So we can use this coercion to convert between A and B. So this is what our API surface would look like and we can represent it. So if you wanted to do the same thing using our manual type representation, we'll have to use something called GADTs. With GADTs, you can constrain the type that you get from the constructors, right? So now type rep has a parameter A. So our Tint has to specify that this is not just a type rep, it's a type rep of int, right? So if we've constrained the type that this type constructor Tint returns, similarly, Tcar is a type rep of char and so on. So we have to tag all of them and then we have to implement EQ type rep like this. But the problem is that pure script does not have GADTs. So we can't write this actual code, but there's a very simple workaround where instead of using GADTs, we use normal ADTs. And we say that along with the data constructor Tint, we also carry again a coercion between int and A, right? So we are saying that it's any type A, but I'm also carrying a way to convert any integer to A and any A to integer, right? So they're isomorphic. So effectively this is the same as saying that A is an integer, right? And this is again our tilde, which is a type coercion. So if you use those type coercions in our type rep, we will basically be able to define this in pure script. And this is just a handful of constructors. You can obviously extend it to as many as you need. So for example, you would need constructors for employee salary and all those things to be able to use it in our example. And then we start writing our EQ type rep using this kind of a constructor, right? So here we have two Tints, right? IA is a type coercion between integer and A and IB is a type coercion between integer and B. And we have to return a type coercion between A and B, right? So how will we do this? We know that because both type rep A is a Tint and type rep B is also a Tint. We know that they are both the same because they're both integers. But how do we create that coercion between A and B? So this requires us to actually look at the internal structure of that coercion, right? So coercion is just an isomorphism. As I said, it has two functions. If we are saying A and B are isomorphic, they are the same, then we can convert between A. We can convert any A to a B and any B to an A, right? So let's define a data type called same. It basically carries two functions A to B and B to A, right? And this same happens to have a category instance. You can obviously define an identity function for this, which basically says that any type is equal to itself. Effectively, it's saying that. So we can define a same AA instance for any type A. And our conversion functions are basically just identity, which don't do anything. And we can also define this category composition function, which basically, if we are saying that we have a same AB and a same BC, which means A and B are the same and B and C are the same, then we also should be able to say that A and C are the same, right? And this is how you would write it. It's pretty simple. So the first same AB means that we have two functions AB, which converts every A to B and BA, which converts B to A. And then similarly, we have a function that converts any B to C and any C to B. And now to get a function that converts from A to C, we first go from A to B and then B to C, right? And similarly, when we have to go the other way, when you have to come back from C to A, we can first go via B. So we can convert from C to B and then B to A. So this value that we create is an isomorphism between A and C, right? And this falls pretty nicely out of this data structure that we created. So it's, so we have these two manipulation functions. And these are basically kind of like proofs, right? Which, as I said, ID is basically saying that each type is equal to itself, which is the identity function that we just defined. And then we can define a symmetry function that says, if you have a proof that says A is equal to B, then you also have a proof that says B is equal to A. And it's extremely easy to define if you have a function from A to B and B to A, if you just change the order around, then you will get the isomorphism between B and A, right? The other way around. And then we have the transitive function, which if we say A is equal to B and B is equal to C, then A is also equal to C, right? And this is just the function that we defined in the previous slide. So we have these tools that allow us to prove different things. And then using these things, we can define our function, right? So here, we come back to the question of how do we define an equiti prep with our manual type representation? So if we have a T end, which has the isomorphism between integer and A, and we have a T end, which has the isomorphism between integer and B, then we can get the isomorphism between A and B by just composing these functions, right? So to go from A to B, we first go from A to I, and then I to B, right? And this is composed, so it reads the other way around. First we do A to I, and then we do I to B, and we get A to B. And similarly, if you want to go from B to A, then we first go from B to I, and then I to A. And we have our conversion functions between A and B, A to B and B to A. And then we can compose our same AB like this. Or using the functions, the manipulation functions that we defined earlier, we can just say, if we have a proof that integer is the same as A, and we have a proof that integer is the same as B, then we have the proof that A is the same as B, because A is the same as I, and I is the same as B using our transitive proof, right? So we can compose these two things together. And we were not given A is the same as I. We were given I is the same as A, right? Where I is integer. So we just use sim symmetry to invert the type arguments. So we can just say that if you know integer is the same as A, then A is also the same as integer. And then we have the arguments in the right order to compose these two things together. So this is an example of how you would manipulate the proofs that you're already given into the proof that you actually need, right? And you can define functions like this for all types, for all the data constructors. Like the integer is here. You can define it for any type that you want, right? So, yeah, so this is again, we'll have a class typeable, which will be defined for all types that you need. So for an integer, our proof will just be identity, which is this identity is the categorical identity that we defined earlier, which is this one. So it's basically same identity identity, right? And then we can define it for array by, again, just doing identity like that. So we can construct type reps, typeable instances like this. And now we have a way of writing this function. We will say that first we check if A is instance of type rep. If it is, then we got a coercion, right? And the coercion is that same data type that we defined. And recall that the first parameter here is the conversion from A to what we, what is our desired type? So A to salary. So this coercion is from A to salary. So we can just coerce it to salary, and then we can apply F on it. And if we got nothing back, that means that A was not of type salary, then we just return A as is, right? So we can define this function finally like this. So an alternate API for this would be defining a cast function instead of a type checking function like instance of or EQ type rep. We can also define a function called cast. And the cast is basically a way to take any value A and maybe convert it to B. And the way it does it is it first checks if A is the same type as B by doing an instance of, and then if it is, then it just converts it, right? By using the conversion function. And then we can write our increase function in, I think slightly nicer way using cast. So we try to cast it to salary. If it is a salary and we got a just. So here N is a salary now. So we can just apply the function F to it. Otherwise we just return A as is, right? So this is a slightly nicer API. The typeable library provides both cast and EQ type rep and instance of, so it provides all of these functions. So we have been working with the same data type, which is here, but the library actually does not use this. The library uses something called libanese equality. So I'm going to go with that for a couple of minutes. Basically, instead of having two separate conversion functions, A to B and B to A, libanese equality states that for any context F, you can convert from F A to F B, right? So you have A wrapped inside some context. You can convert it to a B wrapped inside the same context. And this is just a single conversion function, but it's of the same power as that isomorphic isomorphism that we defined earlier, right? But it's a lot more flexible and a lot easier to work with because it's a single function. So we define our libanese data type like this and we define a type alias for this also, the delta. And then we define a run libanese function, which basically unwraps the libanese and gives us this conversion function back, right? So if we have a libanese equality between A and B, then we can convert it any A wrapped inside the context F to a B wrapped inside the same context F, right? And it just unwraps. So this is what it actually uses. There's a library called pure strip libanese that does this. I think that's what the name is. And again, libanese is a category, but you will see that the instances are much easier to write. So libanese, because it has a single function, so it's libanese, the identity for libanese is just identity, right? If you have an FA and A is the same as B, then you don't need to do anything. You already have an FB. And composition is just, again, function composition. If you can go from FA to FB and you can go from FB to FC, then if you just compose those two functions together, you will go from FA to FC, right? And that's what libanese is. It's a lot easier to write instances for. And this is the proof. You've got just a couple of minutes left. Sure, sure, sure. I'm just going to quickly wrap it up. And this is the proof of how you know that this is the same expressivity, because you can convert a libanese equality, a live data type into a same data type, right? And if you just look at it, this is how you derive it. So you know that they're the same. And I'm just going to skip over this. And finally, we come to the solution we hear this. This is a technique called scrappy boilerplate, which basically generalizes over traversals, over recursive data types, not recursive nested data types. So a GMAPT is like a generalized traversal over any data structure A, which, so using this, we can define a function called everywhere, which can apply a particular function to all leaves or all stages of a nested data structure, right? And when we pass it, our salary increment function, we basically get what we want, right? So this will take a company or any data structure which has nested salaries inside and it would increase salaries everywhere, right? So this is a general way of writing it without having to go into specific nested traversals, right? Some of the use cases for typable is, for example, you can have dynamic types. They're dynamic values. So the way it's done is basically you basically have a data type that both has an A and the type of A, wrapped up inside an existential, right? So the type does not actually specify the A part of it. The type just has the context T, but when you match over the existential and you get the typewrap out, you can use the typewrap for comparisons and figure out what the type of A is, right? And this is the way you do it. You basically pass it an expected type. You say, okay, I have a dynamic T. Is it a boolean, right? And if it's a boolean, it will give me a just that boolean value. Otherwise, it will give me a nothing, right? And you unwrap it and you check if they're equal and if they're equal, it does the coercion and returns it, right? Another way, another use case is serialization, deserialization where you basically store the typewrap itself. Typewraps can be serialized. So you store the typewrap. You serialize it with the value. And then when you read it, you can check, okay, I was expecting an integer and the typewrap I got was a boolean. So type store match, right? So you can have errors. Let me show you an error. So here we get the deserialized typewrap out. And then we compare it with the expected value. And if it didn't match, then we can throw an error which says expected type was this, which was the typewrap that was passed. But I found something else, which is the typewrap that we actually read from the serialization. So yeah. So this is the library. The library basically makes it extremely simple and safe to create typewraps. You don't have to do this manual representation thing because that's unsafe. And it's not very extensible. So the library basically just says that you can define your own takti instance. Takti is a class that the library provides. And it's a very manual instance. And the compiler also makes it impossible to get it wrong. You cannot write an invalid instance for this. It will not compile. So you just define an instance like this. And it will automatically generate a typewrap for you. And it does it using F5 hacks that I'm not going to go into right now because you don't have time. But basically it makes it very safe to actually generate typewrap and typeref for types. So yeah. So this is the F5 stuff. I'm not going to go into it. But this is the library. Please check it out. And I'll be happy to take any questions. Thank you so much for sharing about this with us today. It's been lovely to hear your insights.