Check out a free preview of the full Functional-Light JavaScript, v3 course
The "Fusion" Lesson is part of the full, Functional-Light JavaScript, v3 course featured in this preview video. Here's what you'd learn in this lesson:
Kyle discusses fusion, a common situation in functional programming where functional methods are chained, and the practical ramifications of chaining in performance and readability. Kyle demonstrates an instance of fusion using map and compose.
Transcript from the "Fusion" Lesson
[00:00:00]
>> Kyle Simpson: So now hopefully you're feeling a lot more confident having practiced in an exercise, practiced the whole map and filter and reduce thing. But one thing that we wanna know is that it's going to be extremely common as you start using these utilities more often. It's gonna be extremely common that you end up in a scenario where you have say a chain of a map and then a reduce and, or a map and then a filter and then reduce.
[00:00:24]
Or two or three maps, and then another filter, or a map and a couple of filters and another reduce. Those sorts of chains of things are gonna happen very often. And by the way, those are good things. It's good if your mapper or your predicate or your reducer is a single responsibility thing.
[00:00:40]
That's a good outcome. But there are some downsides to having a chain of several maps and then several filters and then several reducers. One of those downsides is from a performance perspective. You have to remember, especially if we're thinking about larger data structures, things that aren't just a few values in an array.
[00:01:00]
You have to remember that each time one of those map or filters or reduces gets called, it's producing a whole new data structure as a result. So if I have a chain of ten of those, then I'm ending up producing nine intermediary results of that data structure, nine intermediary copies of that data structure, and those copies are then thrown away.
[00:01:21]
So not only is the extra processing of going through the list over and over. If it's 1,000 items, then we've gone through it 9,000 times. But it's also the garbage collection overhead of throwing away those nine extra copies of the thing that we don't actually need. So from a performance perspective, there is a particular concern if you have several items, or if the data structures involved are gonna be rather large or could be large.
[00:01:46]
There's a processing problem there. But I would say there's an even more important or at least as important issue, which is that if you're listing out these steps, step by step by step by step, it's a little bit more difficult for somebody to track, what is the flow of my data?
[00:02:04]
Even a vertical chain of things, they're still having to step from line the line to line. And when we talked earlier in the course about flow of data, we said that a much more declarative way of describing how data is gonna flow is by using something like composition, or a pipe.
[00:02:21]
Compose or pipe makes it clear where our data flows are. And so functional programmers, instead of seeing or liking a long chain of method calls, they're going to prefer a composition. So let's consider this usecase. Let's imagine that we add several maps that ended up together subsequently off of a chain off of some array.
[00:02:47]
Well we know the add1 and mul2 and div3 because they're mapper functions. They have a shape that is compatible with each other. They can be interchanged with each other, and if they can be interchanged with each other that means they can be composed together. Remember that whole idea of composition associativity.
[00:03:08]
That means that if I were to group add1 and mul2 together in a composition, that would be the same as if I grouped mul2 and div3 together in a composition. And those are both the same as if I put all three of those together in a composition. There's an associativity involved with composition.
[00:03:26]
And I just wanna take a moment to remind you, because it's extremely common in programming specially in JavaScript, you don't get these benefits that we keep talking about just because you use .map. You get these benefits if you adhere to the principles of functional programming. It's extremely common for people to call a .map, but inside of their mapper function, they're accessing something on the outside because it's too hard to figure out how to get that into the mapper function.
[00:03:56]
So they just sort of cut corners and they say, I'll just access some value on the outside. And that sort of side effect impurity invalidates all of the mathematical assumptions that go into what we're talking about. You cannot assume that things compose nicely. You can't assume the associativity of composite.
[00:04:15]
You can't assume any of those mathematical principles if you violate the most fundamental of all the principles, which is that this only worked for functions. So it is critical, it is important, I wanna reemphasized, even though it's much, much easier, [LAUGH] it is much easier to reference something outside of your .map call.
[00:04:35]
I see people all the time do a .map call and they're basically adding elements into some other array outside of it. And it's essentially like a .forEach but they don't feel good about using .forEach so they use .map in an impure way. So we're just using this as a looping API.
[00:04:52]
We're not using it to mean anything significant or semantic like it's mapping data structures. Just because you use .map doesn't make you a functional programmer, okay? Using functional principles in your code is what gives you those sought of guarantees that essentially avoid entire classes of problem that could otherwise happen.
[00:05:13]
We want to avoid those kinds of problems. I don't want to make easy to find bugs if I can just make it easy to never have the bugs in the first place. You see how the ladder of those two is far preferable if it's possible to do so.
[00:05:28]
So it's important that we think about and reemphasize, these have got to be pure functions, in our case they are. And in that case that means that we can take advantage of composition here. Those three functions can be composed together. And that's a technique we call fusion. So if we take those three, add1, mul2, and dive3, and put them into a composition, and use that single resulting function as as a .map call.
[00:05:53]
What we have said to the reader of this code is, you don't need to go track the data flow, I'm declarative telling you it's a composition of these three in this specific order. We're gonna go add1, then mul2, and then div3. And if you visualize, I wish I had the animation skills here.
[00:06:10]
If you visualize the numbers, the number two moves through those. The number two goes into the add1 and then that result of three goes into mul2, and that result goes into div3. Those numbers, those values that data is flowing through that stream of composition. And that's how we're ending up with the new data structure that is 2, 4, 6, 8, 10, 12, 14.
[00:06:35]
So if we have a chain of maps together, composition is a much better way of declaratively saying what all of those steps are gonna be. Rather than three .map calls, it would be preferable from functional perspective to use composition. I should also point out, and I definitely cover this in a lot more detail in the book, so I'll just briefly mention.
[00:06:55]
That generally speaking, functional programmers don't like to see methods on values. They don't like to see an array and then a .map call. And the reason they don't like to see that is because that is essentially from the broader perspective that essentially falls into this idea of class-oriented thinking.
[00:07:14]
We have this value that has data. It has behavior associated with the data. There's this class called array that has a method called map on it and we're invoking that array. And the reason why this is a problem is because that map call in and out of itself is impure.
[00:07:33]
And the reason it's impure is because it is receiving as an implicit input of this context that tells it, use this particular array. The reason why that's a problem, the reason why the whole idea of using this aware things, methods on values, that sort of thing, the reason why that's a problem is those are inherently uncomposable.
[00:07:56]
I can't take another map call, a map on this array method and easily compose that with some other function because the input to that map call, part of it is indirect. So I end up having to create a hardbound version of it, do all these other weird workarounds.
[00:08:11]
It makes it much more awkward. The math doesn't work out, if you will. Because we're not really following the true functional principles. So that's why you'll actually see in libraries like Ramda and Lodash and stuff, you won't see a method like .map on an array, you'll see a standalone map method that takes both the mapper function and the array as explicit inputs rather than one of them being an implicit input.
[00:08:38]
That makes those inherently more curryable, more composable, all of these principles that we've been talking about, okay? So we're using the array, built-in array map and array-reduce, we're using them for convenience sake in the illustration here. But you should be aware in this bigger sense that you're traditionally gonna be wanting to use these standalone function versions of all these utilities rather than the method versions.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops