Check out a free preview of the full Hardcore Functional Programming in JavaScript, v2 course
The "Transforms & Monad Patterns" Lesson is part of the full, Hardcore Functional Programming in JavaScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:
Brian explores various transformations, such as type transformations, free monads, and monoids with both the either and task monad.
Transcript from the "Transforms & Monad Patterns" Lesson
[00:00:00]
>> Continuing on our tour of definitions before we build some stuff. And I'm just going to be talking at you for the next 45 minutes or so, ask questions. Cuz I don't want this to be my dissertation about all the problems. We get into stuff. We'll just have fun from here on out.
[00:00:19]
Okay, so next kind of interesting situation. So actually, I'm just gonna go copy this boilerplate. All right, so we have this function httpGet, and you can pretend it's actually going to do stuff. We're just gonna stub it out with task.of. Fun story, if you use id.of, you can actually see things, and you can stub out the exact same interface, and you don't have to worry about it being kind of opaque like a task.
[00:00:49]
It's kinda cool. But what's gonna happen here is we wanna call getUser, getTimeline and getAds. We wanna hit all of those. And in promises, you have Promise.all to do that. You could say get the user, get the timeline, get out whatever. And what you'd end up with is an array of a promise of an array of results, right?
[00:01:20]
Result, result, but without promise.of, we were in a map over this. And we get each function, and we run it. We end up with an array of promise1, promise2, promise3, and they wouldn't be the results. So effectively, you have a promise on the inside, an array on the outside.
[00:01:41]
And we want a promise on the outside, and an array on the inside. See that kind of distinction? We've got an array of promises, instead of a promise of an array. And in the way you'd write this in a type is I have the type of this thing is an array of promise, right?
[00:01:58]
So we wanna flip that into a promise of an array. Promise holding whatever string we have here in this [INAUDIBLE]. All right, so promise.all does that for us, but in a very specific way. We're gonna take a step to make it more general to work on any two types that are capable of commuting flipping around.
[00:02:24]
So if I have the situation right here with tasks in a list, the same thing I wanna task of results, not a list of tasks right? And that that interface is called traversable. We're able to traverse the types, kind of leapfrog them, flip them around. So we're gonna call it traverse, instead of map.
[00:02:52]
And traverse takes two arguments. The first one is point. What are we talking about? What are we gonna have on the outside? It's largely ignored. But in the case of errors where it never actually runs the function left, you still need to know what type you're gonna put it in.
[00:03:07]
So you have to provide it. Type systems can do this for you. So if you go to something like Haskell or pure scripts, you don't actually have to provide that, cuz it knows the types. But here, you do. So what's gonna happen here is this doesn't have a traverse method.
[00:03:21]
It's a list. It's a array built in. So I brought in my library, which defines traverse. And this is, how do I say this? This is a pervasive. This function exists amongst all these functional languages. It's not just like my library. This is just one one way to implement it.
[00:03:43]
And I did that so we could have something with the class, but you'll see this around. In Swift, you'll see this function right off the bat, right? And what we get here now is we run all these. We'll just fork console.log, console.log. We should have a freaking mutable JS [LAUGH] x.toJS, cool.
[00:04:06]
And so we have an array of results. So it's effectively a task over a list of results, instead of a list of tasks. Let's do the same thing, something else.
>> On the list, and kind of promise.all, the JavaScript promise.all, if there's a single failure in the array, the entire thing fails.
[00:04:27]
Is that correct? So are we avoiding that behavior in immutable JS?
>> I think it would cancel it out. Interestingly though, concat can get you around that.
>> Yes.
>> With monoids and alternatives. But yeah, this is just basically promise.all for now, yeah, we'll hit this one. Yeah, thanks for mentioning that.
[00:04:52]
So here, we have this example. Let's look at a different type. I'm just gonna copy all these out from my previous doodle links. Okay, let's get rid of this stuff. And here, we have a very similar situation. Again, we have a list of eaters. We're gonna do is say I wanna take a value, this email, blot@yahoo.com is my email address, don't exploit it.
[00:05:23]
And then I want you to run each of these on there. And what we'll end up with is just this array of ethers. Either either, and we want an either array results. Now, I've used array in both of these examples, but it's not exclusives to array. We could do this with box and Either.
[00:05:44]
Unfortunately, you can't take a nested task. You can't start with tasks on the outside, and put something outside of it. It's not traversable, it doesn't implement that interface, it's impossible. So I can't show you that with task. But by and large, this is a generic interface for flipping two types around.
[00:06:03]
I have a disk type inside of that type, I want that one on the outside. I just happen to be using list on both of these examples. So what we'll do here is we'll use list, and we'll switch map to traverse, do either .of, double r, sorry, I'm fine, it's okay [LAUGH].
[00:06:24]
And then we'll res.fold, or yeah, fold console.log, console.log. All right, and again, and [LAUGH] shouldn't be deleting these things. There you go. And you see the result twice, cuz that's what I'm doing, I'm saying just return it back. This is a common pattern in something like validation libraries, where you say well, return the result or the error, cuz the error is gonna short circuit, or concat with each other, combine, or whatever.
[00:06:55]
And so if it comes back with a bunch of results, just take one, cuz it's the actual thing. Otherwise, it'll just blow up in a nice, clean, mathematical way. One thing I was gonna say is, yeah, if I break this, you go, not an email. It has to be greater than 5.
[00:07:15]
Let's pick that one. Well, that's great. There you go. So both, no, not both [LAUGH] now using monoids. Yeah, we didn't talk about monoids. We're gonna be talking about monoids in the functional architecture course. The point being that this flips types around. And that's something that's useful. You'll end up with situations like, let's just do this with just some fun types.
[00:07:44]
I have an either of three, right? Or I have an either of a list of four. [LAUGH] and from there, I wanna do something where I chain this. And I take this list in, and then I get another either here of three again, or something. And what we'll get is this either list, either thing.
[00:08:14]
And what I can do here is if I put this type outside of the list, I can squash them down. Right, I would end up with an either of an either of a list. And now I can flatten to just an either of a list. They cannot flatten that, but they can flatten two in the row.
[00:08:40]
This is getting really intense. This is much more like advanced material, where you're starting to nest these things, and deal with collapsing them, and flattening them. So you have a nice, but you're being honest in your program. You're saying this could blow up, so you have to deal with that.
[00:08:56]
And you're like I don't wanna deal with it, it's annoying. But [LAUGH] it's just the truth. So having to deal with these things, just, this is just different API for dealing with it than a bunch of try catches. It's just flipping around, and shuffling these, and crunching them down, and combining them in different ways.
[00:09:13]
Does anybody have any question? I didn't really go too far into this. But we have an either of a list, and we're gonna get another either out. Or sorry, not chain, we like map, and it'd be either list, map, list, either mixtures. So that situation ends up happening, and it be good to know.
[00:09:33]
Another situation that ends up happening is we find this natural transformer show. Haha, this is gonna be fun. Okay [LAUGH] yeah, this is the good stuff. All right, buckle up. So what we got here in a nutshell is a database. That when you give it an id, I guess if the id is greater than 2, we're gonna actually go find it and make this fake user.
[00:10:15]
That's friend id is the id + 1. Otherwise, we're gonna say not found. So this is our fake database with a fake find and a fake user. Awesome, and then we have this send. So I guess this is an http response. We're gonna send the code and the JSON of the user.
[00:10:31]
Okay, great. Look at this, kicking butt. [LAUGH] so what's gonna happen? Let's walk through this. We're gonna find a user. What does that return to us? That returns us a task of the user right away. It has to be greater than 2, 3, there it is. Okay, so we have a task of a user, and we get that there.
[00:10:56]
And so what's gonna happen is we get this either of a user, right? This is inside the tasks, that's actually not here. It's not a task of user. That's a task of an either of a user, right? So it's an either user. It's not a nod to GPO [LAUGH] what is that whole data privacy thing, GDPR.
[00:11:21]
So we're gonna fold out the either, and we're gonna say, well, since we're chaining, because we wanna do another database find, the left side has to like turn into a task, because we have to return a task, even if we have to return a task, no matter what.
[00:11:39]
So if it was not there, we'd still have to return a task. And we'd probably wanna do reject it, or something here. But then we get this user, and we do the second find, okay? And then we're gonna fork it here, we'll get the error. Now, this error is a database error, not the couldn't find a user.
[00:12:01]
And also, that's if the task itself gets rejected, not if we return to left. Then here, we get another either user, and we fold it out. Send these, and this is just not fun to work with when you have two nested types like that. There's a lot of different ways to handle that.
[00:12:19]
And I'll be talking about in the functional architecture class about how to normalize these types in different ways, and techniques for doing that. But for right now, I mean, you don't really need anything more than this stuff, but we're gonna arm you with this. So this will be one solution that nails most of the cases.
[00:12:44]
So this is called a natural transformation. In natural transformation, we're taking either into a task. Why is it a natural transformation? Why is this fancy? Basically, what we're gonna say is we have some type F holding an A, and we're gonna turn it into a type T holding an A.
[00:13:03]
And we cannot know anything about A. People talk about encapsulation in programming by making things general, and not knowing anything about it, that's the same as encapsulation. Polymorphic functions are encapsulated functions. They can't call anything specific. They don't know anything about the value. So therefore, it's abstract, and you're hiding information.
[00:13:32]
So it's a type transformation. Natural transformations are a principled type transformation from type F to type T, both holding the value here. And what we're doing is folding the either out into a task that's rejected, or a task that's resolved. So we've taken an either, and we've turned it into a task.
[00:13:52]
So we're gonna see how that's working right now. It's folding the either out. And on the left, it turns into a rejected task. And on the right, it turns into everything's good task. There's laws. There's laws around natural transformations. So if I map, and then I do a natural transformation, that's the same as doing the natural transformation formation, then mapping, right?
[00:14:18]
And that's if this holds, it is indeed a natural transformation. If this does not hold, you do not have a natural transformation. Good kind of guidance is if you touch anything on this value, then you don't really have a natural transformation, as far as the value the eithers holding.
[00:14:35]
You can mess around with either, or whatever type you're dealing with, but not what it's holding. That's what the natural part comes from. So let's go into this, and refactor it using a natural transformation. So we don't want a task of an either of a user. What we can do is chain.
[00:14:54]
We're gonna call either to task. What is that doing? Well, it takes our either of a user, passes it into there. And then what we get is a rejected task, or a result task like happy task. And if we mapped, we'd get a task of a task, but we're chaining.
[00:15:11]
Now we just have the task of a user. So you take this out, make it nice and first class right there. But now we have a user here. We don't have to deal with any more of these folds, right? That's nice. How nice is that? And same problem down here, right?
[00:15:34]
What if instead of down here, we do all this nonsense, we'll just go ahead and chain another user to task, right? Then we'd just get a user out, and we don't have to fold, and we can just send this whole user right down. And where are we at?
[00:15:53]
There we go, cool, everybody's happy. Let's do this though. What happened? Error, not found. One thing that we lost in this, is that folding behavior just rejected the whole task itself, right? It didn't reject specifically that you didn't find the user, but the DB connection worked, but whatever.
[00:16:16]
So by normalizing this on the task, you're gonna fall into the task error, so that is one thing you're losing here. I mean, but look at that, that's so much nicer, right? But also, this is kind of annoying, and there are other ways to handle that. Traversing, and if we have an either of a task of an either of a task, well, I can traverse either, but I can't traverse task.
[00:16:46]
And so there might be a way to get this either in there, or out there. And then we can squash them down, and play around with these types. Natural transformations just make it easier. Another solution besides traversable would be doing stuff like normalizing all these into the same monad.
[00:17:09]
So we can make a new monad called TaskEater, and that is what you would do, use a new monad transformers for, which we'd be talked about in the architecture class. So this is much more of a tool to say I keep running into TaskEater. Why don't I use a monad transformer to make a new thing that's both of them?
[00:17:30]
And then finally, there's stuff like free monads, which again, and monoids, both are good solutions to this problem in different ways, in different situations. So I'm not gonna go into into that today. This was kind of the scope for this class. And I'll be honest, I programmed for years without knowing anything more than this, so.
[00:17:52]
And it was building totally rich working fine applications. So don't feel like you're like, I'm missing out, I need to know transformers. They hit a specific case and specific time, and all the other ones do as well. But there is more to know about shuffling types about.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops