Create Map and Filter Transducers

InstructorPaul Frend

Share this video with your friends

Send Tweet

In our previous lesson, we created map() and filter() functions based on reduce, but we still couldn’t compose them together.

In this lesson, we’ll solve this by making these functions into transducers. This will let us compose the reducing behavior of map() and filter(), without coupling this composed transform to a data type.

Orestes Carracedo
~ 7 years ago

Great stuff. The content on egghead is just miles above anything else out there. Thank you Paul!

Roberto Alencar
~ 7 years ago

Really awesome, thanks for this great content

peterschussheim
~ 7 years ago
hao
~ 7 years ago

great content, Thanks!

BTW, watching this video made my bought Quokka extension... ;)

Paul Frendinstructor
~ 7 years ago

Thanks for the kind words guys! @hao yeah Quokka is pretty awesome. Bought the pro version myself as well.

semmi verian
~ 7 years ago

May i know where is the source code for this courses? thanks

Jakob
~ 7 years ago

Maybe I don't see the reason to why create all those abstractions, but wouldn't:

const isEven = number => number % 2 === 0;
const double = number => number * 2;
const filterEvensAndDouble = (acc, number) =>
  isEven(number) ? [...acc, double(number)] : acc;

do the trick?

Grzegorz Laszczyk
~ 6 years ago

This is where I get lost. To many returned functions. Is there a chance to understand that?

Paul Frendinstructor
~ 6 years ago

@grzegorz Easy to get confused, there's a few levels of higher order functions going on. Is there a specific point you get lost at? It's a bit hard to help without a bit more context.

@jakob sorry for the late reply. Missed your question. What you've done works fine. The only problem is you're baking in logic for how to filter and map into your function. Lets say you wanted another function that filters based on some other logic that isn't isEven, you'd then have to rewrite the logic for how to filter again in that function. It's likely the logic for how to filter is what you'll be reusing more than the business logic for what to filter by, which is why we want functions that abstract away how to map and how to filter etc.

Grzegorz Laszczyk
~ 6 years ago

@paul, I can understand without problems to the point we creating map and filter as reducers. Problems start when reducer is added. It looks incredibly logic and great but is just mind blowing when I'm trying to remember how to construct and call this functions (like in 03:14 of this lesson). And why do we need this pushReducer?

But overall great content so far. Thank you.

Paul Frendinstructor
~ 6 years ago

@grzegorz thanks for clarifying and sorry for the late reply. Ok, so at 3.14 we’ve modified filter to be a transducer, or you can think of it as a function we can use to create a reducer with specific behaviour. The first time we call it we set the transform behaviour, and the second time we set the reducer behaviour. So when we’ve called it twice, we’ve “pre-filled” all the behaviour we care about, and we’re left with a reducer that will operate according to the logic we’ve set in our first two calls.

The only thing that’s decided before you call filter it is that it will filter based on some logic it doesn’t know about. if this logic returns true, then we will return the result of calling reducer, otherwise we’ll just return the accumulation, thus skipping the value. once you’ve called filter once, you’ve decided what logic the filtering should be based on, in this case the evenOnly predicate. The function now expects a reducer so that it knows how to handle the logic for building up the accumulation. And what we’re doing on that line is passing the result of map(doubleTheNumber) to be the reducer that handles all the values that pass the evenOnly predicate. At this time, map doesn’t need a reducer itself, we’ve hardcoded in how the value should be built up (by calling push).

But when we modify the map function at 5:42, it no longer knows how to build up the value. That’s where the push reducer comes in. We’re basically adding back the logic that was in the map reducer initially, but it comes through the reducer argument. This means we can use map with different logic for building up the accumulation. For instance you can call it with a function that uses accumulation.concat() instead of push, or as you see in later lessons, with different data structures than arrays.

Hope that helps.

Bress B
~ 6 years ago

I'm curious why the composition

Bress B
~ 6 years ago

Not sure if I can edit / delete the premature post above but I'm curious why the composition in [1,2,3,4].reduce(isNot2Filter(isEvenFilter(doubleMap(pushReducer))), []); /*?*/ [ 8 ] operates left to right? My understanding is function compositions operate from innermost function to outermost function. Thank you -- really learning from this course.

Paul Frendinstructor
~ 6 years ago

hey @Jesse, sorry for the really late reply. Missed this over the Christmas period :).

You're right in that functions are evaluated from innermost to outermost. However, we're passing the result of calling isNot2Filter with a reducer into .reduce. This function is filter with both the predicate and reducer applied. So if you look at filter, you see that it will evaluate the predicate before calling the reducer.

In detail - If you look at isNot2Filter, it is the result of calling filter with a predicate for filtering out the number two. So isNot2Filter is filter but with the predicate already determined, and it now expects a reducer. However - when you call it again with a reducer - it returns a wrapping reducer that will check against the predicate first - before calling the passed reducer.

So in short - the composition evaluates from innermost to outermost - but those calls just create wrapping reducers that will call other reducers. isNot2Filter gets called last, meaning the function it creates will be called first when .reduce calls it.

Hope that helps.