Rewrite Map and Filter as Reducers

Paul Frend
InstructorPaul Frend
Share this video with your friends

Social Share Links

Send Tweet

Two of the most useful operators when working with collections are .map() and .filter(). We want our transducers to be able to combine these operations in a single pass when iterating through our collections. To do this we need to find a commonality between the two, so we can treat them the same. To achieve this common ground, we'll create map() and filter() functions which use the built in .reduce() function under the hood instead, and we’ll start to combine the concepts of transforming and reducing.

Lecturer: [00:00] Let's start with a transformation that doubles numbers. Let's call this double the number. Then, we'll take a number and return the number times two. Then, we can use that with the map operation like so. Our output here is an array with doubled numbers.

[00:21] When using the built in map operation, we pass a transformer which describes our transformation. The logic for how these new values are added into the resulting array is abstracted away from us. This is a great thing since we only care about how the transformation happens.

[00:40] This is all the logic we have to describe. There's nothing here about pushing values onto an array. This transform can be composed, as well, since the composed transform has the same signature. Let's compose double the number with itself.

[00:53] We'll call this double twice, which will take a number. That's just going to call double the number and then call it again. If we use that with an array, we can see we're now doubling twice. We're now iterating through this collection once, but with a composed transform.

[01:15] Now, let's see if we can extend this and add in a filter operation. Let's create an even only predicate that's going to take a number, but it's only going to return true if the number is even. We usually express that as number modular two equals zero. Calling even only with one we get false. Calling even only with two we get true.

[01:42] Now, let's try and compose this with double the number. Let's call this double and even and that will take a number and return double the number, which will call even only, which will take a number. This isn't going to work because even only when given a number is going to produce the value true or false, but double the number is going to try and double that. We've got a mismatch in our signatures here. I'll prove that to you first.

[02:08] Let's create another array. Let's call filter on that and pass double and even. Our output here is the value two, but like we said, double the number has done nothing as even only would have returned true for the number two. Then double the number would have tried to double the value true. We've got a problem here.

[02:29] We've established the problem is that the built in filter takes a predicate and the built in map takes a transform. We need a way to handle both map and filter where there's some sort of shared commonality, like an internal hook where we can be involved with how this value gets added to the array.

[02:50] It turns out a reducer is a great fit for this. Let's derive map from reduce. Let's create a function called map, and that's going to take our transform and our array. XF is just a short for transform if you haven't seen that before. In this function, we're going to return the result of reducing over our array.

[03:11] Let's return the array.reduce and let's define our reducer. That takes an accumulation and a value. We'll use an empty array as our seed value.

[03:22] Now, let's work on the body of our reducer. All we want to do in here is make sure that this accumulation gets all of our values, but after we've run them through our transform function. That's simple enough.

[03:34] Let's just call accumulation.push and we'll call our transform with our value. Then we'll return our accumulation. Now, let's try and use this with our double the number transform. I'm just going to call map, double the number, and let's pass in an array of numbers. Behold, this works just like before.

[03:56] Now, let's do the same to filter. This function will be fairly similar. Let's define filter, but instead of our transform it's going to take a predicate and our array. We once again are going to return the result of reducing. Let's define our reducer and our seed value is once again an empty array. The shape of this function is very similar to our map. The only difference is going to be our internal logic.

[04:27] What do we want to do in here? We don't want to transform a value. We want to know if this predicate returns true, then we want to add the value to this accumulation. Otherwise, we're just going to ignore it. We just need a conditional push in here. Basically, if the result of predicate with a value is truthy, we're going to push. Then we'll just return our accumulation.

[04:52] Let's give that a go. Just like before we get our even numbers back. This is a step in the right direction, but we still can't compose map and filter together. To do that, we're going to have to add a few more abstractions.