Immutable JS iterables offer the reduced method, a powerful, and often, misunderstood function operator on which map, filter, GroupBy, et cetera, are built.
The concept is simple. Reduce transforms your venerable into something else, that's all. The name is misleading, as you may or may not actually reduce anything.
Let's replicate the GroupBy and filter methods with reduce to illustrate how it works. I've got this TODO class, and then, I'm going to generate a bunch of these TODO classes, five of them, to be exact. Each TODO is going to have a random completion, and we'll use this value to group the TODOs by later.
We have two functions here that we'll use to reduce with. We have transform TODOs, this will be our GroupBy that we'll use to reduce with. We have TransformTODOs, this will be our GroupBy transform, and FilterCompleted, which we'll use to replicate how filter actually works.
Let's go ahead and group these using reduce. We're going to create our TODOs, the TODO equals generate TODOs. Then, we'll go ahead and create our grouped TODOs by doing this. TODOs equals TODOs.reduce, so there's the reduce operator.
We're going to pass in the transform TODOs, which will group them for us. That's the first argument. The second argument is actually really important, for wrapping your brain around how reduce works. We're going to pass in the initial value, which is the shape of the data that we want returned back to us.
When we're passing this value in, we're seeing we want the data to look like this when it comes back. Let me show you what I mean by that. New, immutable.map, we want an immutable map to come back, and then, we want to group it by true.
We're going to have to keys in this. We're going to have a key of true, and it's going to contain an immutable list, which will have our true TODOs. Then, we also want a key of false, and these will contain our false TODOs. This is how we want it to look, when it comes back.
I'm going to cap that off. We'll go up top, and create this method. How does this work? We're going to first create a list, and we're going to take that initial value. That initial value is an immutable map, because we have an immutable map coming in here. If we want to mutate it, or just get a new copy of it, we need to do something on it.
We're going to get value.completed.2strings. What this is doing is saying, "OK, what is the completed value on our TODO?"
If it's true or false, and convert that to a string. That's going to give us that key, the true key. If it's true, now, we have the immutable list coming back that we can now push to.
Push value. Now, we've just added to this list inside of the map. That's what we're doing, we're basically just creating new data. Then, we return the initial value, and then, we set the value.completed.2string with the list. Since it's immutable data, we have to actually update what's going on here. It comes back again, and loops through it.
Now, we should have two immutable lists with keys of true and false, holding the true and false TODOs. Let's go ahead and write some expectations, and see if we have that. Let's say, all our true equals grouped TODOs.get the true key, which we know we have a map that looks like that.
The true key, and then, we'll do an every on it, so every, and then, we'll just check to see if our values, if our TODOs have a completed completion. Let's expect are all true to be true. Sure enough, the test passed.
Every is also a reducer. We're basically saying, as long as everything is true, return true. If we get a false back, it'll actually tell us that we have a false completion, which means that all are not true. That's a neat way to reduce down an iterable to one value.
We should also see the same thing for, are all false. We'll take our grouped TODOs.get are false, are a false key. Then, we'll do the same thing, but we'll say, "Are all these guys false?"
We'll say "val.completed equals false." Then, we'll write the expectation, expect are all false. Let me get this to value, into val are all false to be true. Sure enough, they are all false.
That's how we've now grouped our TODOs by true and false. This is how we replicated GroupBy, GroupBy does the same thing, which is also another operator. So much to reduce, but you can see how GroupBy was written. Let's go ahead and do the same thing for filter.
Let's do let TODOs equals generate TODOs, and then, we'll do let completed TODOs. We're just going to filter out all of the incomplete TODOs.
Filter, and we're going to actually use the original filter to contrast with the reducer version of filter. Value, and we'll say, "value.completed" is equal to true. It's basically saying, "Please, filter out anything that is not completed."
We'll go ahead and check that. Are all true? We'll say, "equals," go down a little bit, equals completed TODOs. We'll do our every again, every, we'll say, "val.completed equals true." Expect are all true to be true. Sure enough, we pass, we're good to go. Let's see how we can replicate this with reduce.
We'll say, "let reduced TODOs," which are going to be filtered, as well, we'll say, "equals TODOs.reduce filtered, completed," which is the method we're passing in. We're going to write that in a second, and then, we want the shape of the data to come back as an immutable list.
The shape of the data is going to be an immutable list. Let's go ahead and write this function. We're going to filter out all of the completed TODOs. How do we do that with reduce? We've got our initial value, which is the shape of the data, and then, we've got the value that's being passed in.
We only really need these two arguments, we're going to skip using key and iterator. Then, we're going to look at the value. We're going to say value.completed, and if it is, we're going to push it onto that initial value, which we know is a list.
We're going to push it on to the list. We're going to say initial value equals initial value.push value. Now, we've updated the data, and then we return initial value, just that simple. This, now will only contain the completed TODOs. Let's check to see if that's the case.
We'll do, are all true. I just realized the should be a let. Are all true equals to reduced TODOs, and then, we'll loop through these every val.completed equal to true.
We need to take this, we're going to say filter completed, and now, we're going to go ahead and check them. Are all true to be true, and there you have it. That's how you write your own filter method.