Check out a free preview of the full Hardcore Functional Architecture Patterns in JavaScript course
The "Monad Transformers" Lesson is part of the full, Hardcore Functional Architecture Patterns in JavaScript course featured in this preview video. Here's what you'd learn in this lesson:
Brian explores the use of monad transformers with the Task transformer which contains a lift method that will avoid duplicating an inner type. A transformer is a monad that merges two monads together. Transformers are needed because unlike functors, monads do not compose.
Transcript from the "Monad Transformers" Lesson
[00:00:00]
>> Monad transformers are not the prettiest thing they don't have a well founded theory and math are just like a way to get through this problem. And we're gonna look at free monads which do have a well founded theory. But this is one compose, I just wanted to show that functors compose, but monads do not.
[00:00:22]
So let's jump into transformers here. All right, let's get into a situation where it would be useful to have a transformer. [LAUGH] So let's go ahead and say, we have an app, and this app is gonna be super cool. Very similar to what we were doing before, we have these nested types inside other types and we run into these problems because of that.
[00:00:47]
So I have a task and an Either whenever I try to find something, it's gonna try to find something in the table given my query is gonna put it in an either saying I might not have found it, we're gonna put that in a task. Because we're trying to model a database kind of interaction.
[00:01:03]
So what we'll do here is say I'm going to find the users where ID is 1, so that's me. All right, Mark's over here. And it looks like I'm following this person over there, and I'm following Mark, and Mark's following me. Cool, so what we're gonna try to do is all right let's map over this.
[00:01:28]
What I wanna do is find my follower. So it's actually a chain because we're gonna do another find, right? Give myself some room. All right, so we get this, Either user, right? And with the Either user in the hardcore functional class, whatever you're doing was running natural transformations to turn the Either into a task so we can kind of crunch them down each time as we go.
[00:01:55]
Here we're gonna take a different approach. We're gonna make this kind of monstrosity, this task Either using monad transformers. But let me demonstrate a little bit further on why we wanna do this. So because I have an Either user, I can't, I don't have a user. And because I'm calling chain, I have to return another task, right?
[00:02:14]
This is a task and Either the user and if I'm gonna chain that I have to return another task. So what I'll do is fold out the Either I don't even know how to do tasks get rejected here, I guess. And then over here, we've got a user, okay, now we wanna find from the following table, where the user_id is my u.id, okay?
[00:02:43]
All right, so find, actually I think it is follow_id is my user id, follow_id, doesn't really matter. This is just just to demonstrate I wanna get this correct. We're gonna find the wrong followers. All right, so cool. But now we have another Either user, right? It's a pain in the butt, we keep running into these Either users.
[00:03:10]
And now we have to fold that. [LAUGH] All right another rejected here, and then now we have our like following. Let's call it fo for now, and I wanna find from the users table, the actual user with ID that comes from the followings user ID. Okay, you see what's happening here, I'm trying to use this link table to find who are my followers and then go back to the user table to find that user.
[00:03:41]
And it's just a disaster I just keep running into these tasks Either and they have to keep folding them out. Let's see what we get as an answer. So what I'm gonna do is fork that error, console.log. And because I ended on a find here. [LAUGH] Or I can another Either, right?
[00:04:02]
So you get a Either user again, once again and then right here we'll fold out either user to a console.error, or console.log. And let's call up. And we have to fork it wait we fork it there that's fine. Just fork it there call it app. Normally you want to fork outside but whatever, c lear Transformers/1.
[00:04:29]
Cool we found Mark so we gave it my ID, and then we found the follower ID. And then we found from the users that user ID from the following and then we get Mark, cool. So how do we solve this with monad transformers? Does everybody understand the problem, like how much this sucks.
[00:04:48]
[LAUGH] All right, the reason why this sucks. [LAUGH] So what we can do is is say, all right, I'm gonna make a new thing called TaskEither. So I'll say task transformer and we're gonna feed it either call that TaskEither. All right, what that did is task transformer created a new task much like compose, created a new type that knew how to map.
[00:05:16]
This thing knows how to change chain. [LAUGH] But it's specific to each monad. So each monad should have, there's an EitherT, and an IdT and so on. So because each monad can only know how to deal with some its own effects, and then just call chain on whatever it has inside.
[00:05:37]
It's like generic to this point, but it's specific here. If I wanna recover task interestingly I can just give an Id now I have tasks back, just kinda neat. That's a little bit of a beautiful property that carries through. So you can define just the transformer and recover the original type just by applying it to the identity functor.
[00:05:57]
But what we're interested here is TaskT(Either). So how do I work with this thing? Let me show you what problem it solves. And then we'll kinda dig in play with the API of transformers here. So we're gonna say TaskEither, and here's something that's a problem. TaskEither will take, if I say of, of on TaskEither will give me a task of an either have an x, right?
[00:06:26]
Task either, and I have an Either here so that would give me a TaskEither(Either(x), they'll see that that's what up does. It puts a value in both types now and I'm trying to put a Either they're not. So what I'm gonna try to do, what I'm gonna do instead is called lift.
[00:06:47]
I wanna lift my Either into the TaskEither, so it's just like of, but it won't duplicate that inner type. Does everybody see that? So of would have given me Either(Either (x). But lift just puts it one level in so that's something that we're gonna look at in a minute again.
[00:07:11]
But that is a thing. So now, if I run this and the ghost of life coding is kind, we will be able to say, all right, well now I don't get an Either user because it knows how to chain correctly over both types. So I'll just get a user, how nice is that?
[00:07:33]
Now we could just find stuff, right? And again, if I chain that, I just get a another user, right? Well, I guess this is up, fo. But yeah, so what's happened here, is now I had a Task(Either) and I've changed it so I still have Task(Either(User)). And again, I've chained that, and I get a Task(Either(User)).
[00:07:58]
And finally, we .fork the task, we still haven't Either user inside the task, that's an important distinction is that task either knows how to chain. But at the end of the day, it's still a task holding an either. So when I fork the task, I get the Either, it's not just gonna give me the user out.
[00:08:15]
It just defines chain on that thing. So now if we run it. All right, it works great. [LAUGH] For change this to user id, we'll get a different user out, yeah let me back out, cool. So questions on monad transformers, we are gonna talk a little bit more about it but I just don't wanna leave this example of what it's solving for, and then we'll play around with more about the mechanics of them.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops