Transcript from the "Defining the Free Monad" Lesson
>> This is where things go a little off the rails. We're going to talk about Free. And full disclosure, Free Monads are usually not what you want [LAUGH] but they solve a very specific problem and it's good to know about and when we're talking about functional architecture. We talked about monad transformer stacks and we talked about free monads.
[00:00:28] And we talked about like these reader type injections. We saw reader injection through ask or dependency injection through ask. And I just wanna show you an example of using Free. And it actually turns out, it was the best solution to the first app that I wanted to build.
[00:00:45] So it's worthwhile to know. So, what is free, the free monad? The free monad is a is a way to take your functions and treat them like data types. So if I have a function like HTTP GET and it takes some arguments like a URL, instead of it going and making a task and trying to like, go do this function right now, we can actually return a data type, like HttpGet holding the URL.
[00:01:22] So you have a data type now that represents going to get a URL. And the reason we want it to be a monad is because we want to say, well, I have this data type, and I want to chain, right? As if it actually did it. And I want to get the contents of that URL, and I could use something like an HTTP POST.
[00:01:46] And we'll do some other URL like analytics, with the contents, all right? And then, what I should end up with is a data structure representing the nested tree of computation. You're basically building an AST. You're lifting your functions into datatypes, treating them as if they're running and then you're able to interpret them later.
[00:02:19] So now we have to create what we call an interpreter interpret over here to actually interpret the status structure. So we're actually gonna build this right here. And what I wanna do is actually bringing in a new library. It's called daggy. We're gonna bring in tag some, great library, highly recommended.
[00:02:45] Daggy, all it does is this whole entire class I was making stuff like HTTP GET, it takes some value, take some x and returns as a data type. We have methods on it. This just allows us to, it's in a shorter syntax for defining these objects. And then it gives me a function to kind of decompose a set of different types and we'll see that in a second.
[00:03:10] So the way Daggy works is I'll say, we have http and we'll call it tag sum. So it's like a sum type, it's like a type definition. Say HTTP is the name of the superclass kind of thing. Then we're gonna say we have a get, and the arguments get has is x.
[00:03:30] How about URL? And then we have a post. And we'll have the URL and the body. So, what we've done here is say, I've defined two data types GET and POST. And actually let's just take a look at that before we go any further. So, if I say Http, Get, whatever, home.
[00:03:54] You can console.log this thing. And it's just like a little data type for my, Own usage. See? It's got a little url home. So it's a shorter syntax for those big object literals I was creating. But what's cool about it is I'm able to say, if I have a get of home and I can call Cata on that and it's a short for Catamorphism.
[00:04:27] I can do a pattern matching to say, well if it's a get, get the URL out and do something, hi. And if it's a post, I have the URL and the body at my disposal, I'll say, post whatever. These two functions, so I can actually run a catamorphism on it and it will pattern match on what type it is.
[00:04:48] And then allow me to destructure the type and do something to it. So these two things together will be using, to kinda short cut this little type of creation library and it allows me to easily digest and match rather than being like, do you have. Otherwise, I would have to say like, I don't know what X is, right?
[00:05:09] Is this a get or a post? I'm not sure. Without Cata I would have to be like, you know, does x have a body and it's probably again, so it makes it nice to be able to pattern match on the type. All right, cool. So we've done is taken our functions get with a URL and post with the URL and body and made it a data type.
[00:05:32] Then we have this little helper function called lift f, it lifts it into free. So if I say httpGet, I can't. What I can do is make these things into monads, just by calling lift F. That's why it's the free monad. It just lifts the type right in, just like an array is the free monoid or you can just throw anything in it.
[00:05:57] We can do that with lift F, can we have this Get, httpGet. And then we have an httpPost, and it takes the URL in the body. And this can certainly be automated. All we're doing is running the constructor and then calling lift F right afterwards. It's just composition.
[00:06:21] But just trying to be complete and show you how this works. Okay, so now let's actually run a little app here. So like app is, we'll say first get the httpGet on home. And then we're gonna chain that with the contents. And yeah, we'll do this post with the contents.
[00:06:44] And that's not httpPost, httpPost. So just just so we're aware, these little helper functions are literally just the constructors and I'm throwing them into lift F. So what we're getting is a free of httpGet, and a free of httpPost here, so that's how I can call chain. So when I call this function, I can get a free monad, I can chain the free monad.
[00:07:14] What's really nice about this is free monads aren't anything but data, they're just a data type and it just normalizes everything, and I can just chain and compose and do whatever I want. So recall result is going up. Let's console.log what we have here. What's going on here?
[00:07:36] So this is a little weird. We have a free monad that has a property x is holding our get object. And then, we have this kind of continuation function. So we're going to do here is pass this into an interpreter. And the way we do that, it's kind of fun.
[00:07:56] If you have a free, free holding some stuff, we can fold map that out of the free, and map it into some other type before that out of this and into something else. So that's what we'll do. We're gonna make a little interpreter. So we have the app, and we're gonna fold map the app into interpret, and let's make a little task, right?
[00:08:25] We'll make a new task because these things are gonna go do serious stuff, right? Actually, you know, I'm just gonna use ID to stub it out, honestly, ID is typically what I do when I'm, kinda architecting, and then when I've got all my types in order, I'll just swap out ID with task, this way I can actually look at what's going on instead of having it in a task.
[00:08:44] So we have our interpret, let's write that. Now takes our type x. And again, we gonna call our Catamorphism on that, and if it's a get, we get a URL. Why don't we do like, you know, got your URL and put this in that. This will be the contents for this url, and we have a post, and it takes a url and a body and we'll say, posted body to this url.
[00:09:28] So, this is interesting. What we've just done is we've written an app entirely with data, and then we've decided at the calling time which interpreter we wanna use. This interpreter will interpret it into strings. It has to be an ID, we have to have a target monad here cuz we're calling full map and we want to be able to put it into another.
[00:09:53] So its Id.of. Should have mentioned that, we should usually have a target monad, it's almost always task. But if you're doing something like these little like, log kind of interpreters, test interpreters, then we can do that. And now let's run it. And what do we get? We get our idea out so we could just call extract.
[00:10:14] Look at that. It posted the contents for home to analytics. So we've we've created a data structure, we could write more on that, I think we could do like chain we are gonna make more get calls and whatever and it's just the data structure and then we interpret that data structure using foldMap, which just cascades down and runs each instruction and interprets it, that's great.
[00:10:35] Any questions, yes.
>> So we're using the Catamorphism as a switch statement.
>> There's a lot more going on under the hood, I know what the monad is but I'm curious if you comment on that. We're essentially using it as a way of doing a bunch of ifs.
[00:11:14] Reads really nice and this is really short so I didn't want to fill up half the screen with like classes or types and then try to like write an interpreter. So that's why we brought doggy in and I highly recommend it. It's great if I actually miss this before it even gets there, I think daggy will tell me.
[00:11:33] Hey, you gave me some stuff and you didn't have post in there. That's really nice. [LAUGH] Like I tried to interpret your type that said ahead get and post and you missed a case. So that's pretty cool. All right, so that's free monads in two seconds. We're actually gonna use that for the first app.
[00:11:52] I really wanna drive home that this is useful, I find it very useful when we did an app where we were pushing the NPM back when we had Bower, y'all remember Bower, what a world. Anyway, [LAUGH] We're like posting like GitHub packages and all this stuff and we're doing all this like really nasty processes with all these side effects and it was almost impossible to separate the logic from the side effects.
[00:12:22] Cuz it was literally all side effects. So it was like, how do we test this thing? How do we work with this thing? How do we make it so like, I'm not posting to NPM every time I run it like that sucks, like I really want to build this app out, but I don't want to like accidentally publish things.
[00:12:38] And this was a great solution for that. And it came in handy then and then I haven't had to use it since. [LAUGH] So like, it's there for you if you need it and it's really useful.