Learn how to use array reduction to create functional pipelines by composing arrays of functions.
If you put these two ideas together, something interesting happens. Before we dig too deeply into that, I'd like to set the stage here. I'd like to talk about the kind of operation we're going to be doing and what would be a naïve way to do that.
Let's say we have a bunch of simple mathematical functions. We're going to be able to increment and decrement a value, and we're going to be able to double and halve a value.
Now, let's say that, for whatever reason, we have this requirement that we're going to get some input value and we need to increment that value, then we need to double that value, and then we need to decrement that value.
One way that we could do that would be to say our initial value = 1. Our incremented value = increment initial value. Our doubled value = double incremented value. Our final value = decrement doubled value.
If we say cancel.log final value, we see that the final outcome here is 3. I hope that this is setting off a whole bunch of alarm bells for you. You wouldn't actually want to do it this way.
You're initializing all these different variables. You're making your code much more complex to read, and you're introducing mistakes. What if I accidentally decremented my initial value because I totally just boneheadedly forgot that I'm changing my variables or whatever?
This is not the way to do this. You think, "OK, I'm a good programmer. I'm going to write a single function which captures this behavior and doesn't pollute my code space and my scope with all these other variables."
Instead of doing it this way, I'm too smart to use all of these small functions up there. I'm going to write a function called transform. It takes an input, and it returns (input + 1) * 2 - 1. I'm going to say our final value = transform initial value. Same answer.
This is a little bit better, but I would encourage you to consider, in this highly contrived example but also in real examples that you will encounter in your real day-to-day lives, another approach, which is don't take all of this complex behavior and stick it all in one line that just has to work and don't think too hard about it later if it's not working. Just cross your fingers and hope for the best.
Let's be good functional programmers here, and let's use functional composition, which means that, instead of writing a function like this, we're going to create what's known as a pipeline.
You might hear this term thrown around, because pipelines are really popular right now. They're useful in things like stream computing. They're useful when you're doing big data analysis. All the Reactive stuff just loves this.
A pipeline is a term for a series of functions that get applied to some initial value in order to return some final value. This pipeline can be invoked as just one single function, so you don't have all these intermediary values littering up your code base.
The behavior we want to capture is we want to increment a value. Then we want to double that. Then we want to decrement that.
Our pipeline is just defined by listing the three functions in the order that we want to use them. Now, we can say our final value = pipeline.reduce -- we're going to get to reduce eventually, right -- which takes the accumulator and the function and takes the initial value.
Remember how reduce works. The accumulator is whatever got returned the last time this function ran and FN in this case. This is the value in our array. When we call this, FN, the first time, is going to be increment, and the accumulator is going to be the initial value.
We're going to increment the initial value, which is 1. That should give us 2. Then the next time this is called, the accumulator is going to be = to 2. The function, we want to apply the double function, so that's going to return 4. Then we're going to pass 4 in as the accumulator and decrement as the function. In order to make this work, this is super simple. Watch.
We just want to say return an application of the function to the accumulator. Now, when we run this, we see that it gives us exactly what we want. Now this is way cool. This is so much cooler than you might think, because this is totally composed. We can look at this pipeline right here, and what do we notice about it? This is an array.
That means that this array can be programmatically generated. We can have some other piece of our code base somewhere. Maybe for this particular use case, because it did a bunch of look-ups, the pipeline is increment, then double, then decrement. Maybe if some flag somewhere else in our system is set, we want to increment it three times, then double it, then increment it, increment it, and halve it.
Imagine if we were doing this the other way where we had all of this custom logic and this big, complex listed mathematical expression. You couldn't just easily mutate that. Further, you couldn't look at it and see it as just this set of very simple functions.
You can read this. You know you're going to increment something three times. You're going to double it, increment it two more times, and then halve it. When we run this, we see the answer is five instead of three. This should be reminiscent if you're familiar with Unix pipes. It's almost like we just did this.
That's pretty awesome. The outcome...We don't care about anything that happens in the middle. What we care about is the final output of that process. Some people like to think about this in the opposite direction.
I just would like to take one last second here and remind you that reduceRight is also a function, in which case you're going to halve the value first, then increment, increment, double, increment, increment, increment. ReduceRight means, rather than starting at the first value, start at the last value and work backwards. If we reduce this to the right, then we get that.