Transcript from the "List Composition" Lesson
>> Kyle: Another thing we might want to do with a list of values is to compose those values together. Remember I said earlier, so I want to remind you, we're not talking when I say list composition, I don't mean composing two lists together. We have a method for that it's called concurrent.
[00:00:19] But that isn't as traditionally thought of from a functional perspective. So what we're talking about here is composition of items within a list. Either a full composition, which is what most people mean by it or, sometimes it's just a partial composition. That's also valid. And that goes by the name in functional programming of typically it's called a reduce.
[00:00:45] So what are some examples of a reduction that we might do, a composition that we might do across a list? See I have a list of numbers, remember I had a transform where I multiplied all those numbers by 2? What if now I wanted to say I want to take this list of numbers and add all of them together.
[00:01:03] I want the summation of all the items in my list. You can express the summation of items in a list as a reduction, it is a composition of values. Now, what do I mean by that? I'm not trying to be too terminology heavy here, composition is when I take value A and value B, and I smush them together to get value C.
[00:01:21] Whatever that means. So for numbers it might be adding, or it might be multiplying, or it might be dividing. For functions it might be passing one function into another function. For promises it might be chaining one promise off of another promise. There's a nearly infinite number of ways that you could think about taking any two arbitrary values and composing them into some third value.
[00:01:44] Again for illustration purposes we are using numbers, but this is a more general concept, composition of values, and we turn to the reduce function. Now, just as a quick heads up, the reduce function is more complex than the previous two, that's why I did the first two. It's more complex than the first two, and it is possible to use reduce in a non-pure way.
[00:02:12] In fact, it's possible to do all of these, but it's kinda easy to do non-pure programming with the reduce function. So, it's a tool that has more power to it, but also a tool that you have to use responsibly if you want to functionally program. So assuming that you're trying to be functionally program minded about it, or as I call it functional lite programming, assuming that's your goal, you have to be a little bit more careful about how you use it.
[00:02:41] So list composition or reducing, what would that look like? Well I could have a predicate function like mult. And importantly here, because I'm doing a composition, that means I need two values. So I'm gonna have an initial value, which I'm calling x here and another value I'm calling y.
[00:03:00] Now I've named them x and y because in the problem domain of multiplying we usually think about x times y. But if your problem domain was something different, you might name those parameters differently. The most common name for those parameters to the predicate that's used in the composed option or the reduce function.
[00:03:18] The most common name is they call the first one the accumulator. And the second one, the value. So let me explain how that works. By way of looking at the implementation of it that I have here called compose. You notice compose actually takes three parameters, not two. Takes an array, it takes a predicate.
[00:03:36] But it also takes an initial value. Why? Because this is still going to be called against every element in our list. But the first element in the list needs to have something to compose with doesn't it? We haven't done any composition yet. And we're gonna pass it into a composition we need to pass something else in.
[00:03:56] You know what we're gonna pass? An initial value. And then those would be composed together and now we have a running total, which is why this is oftentimes referred to as an accumulator. We have a value now that was the result of the first composition, and then we have the second item in the list, so we take those two and pass them in.
[00:04:16] And those two get composed. And now we have this as our value and the third item in the list and those two get composed, so that's how it works. That's why we need an initial value. What would the initial value, what the most appropriate initial value would be for summation?
[00:04:32] Zero. What would be the most appropriate initial value for multiplication? One. Everybody see why? Cuz those are both the identity values in those operations. Zero doesn't make a change to this summation, one doesn't make a change to the multiplication or the division. So, great, excellent. What about a function composition?
[00:04:54] What would be a good initial function? A which returns everything back that it got. So the more complex your value types, the more you have to think carefully about what would my initial value be. If I were doing a composition of promises, like chaining them together, what would my initial value be?
[00:05:16] An initially resolved promise, for example. So when we're thinking about composition you have to also think about the initial value, that's an extra complication. I've got my total here, which because I'm doing math, I can just do mathematics here. I would need to have a more complex implementation if I were doing something non-mathematic.
[00:05:38] Well maybe not a different implementation. But I'd probably call the variables different. I wouldn't call it total or whatever. But you'll notice that I call the fn function with whatever the current value of total is plus the new value. And then that's what gets assigned to total. So that's how I do that whole compose, compose, compose thing.
[00:05:55] And then I return total back out. So down I'm calling it on line 12. I just passed in the array one, two, three, four, five. My predicate mults and I start with one because that's the most appropriate initial value for this operation. It's important to know that this is what we would call a full reduction or a full composition.
[00:06:15] Meaning that we started out with a list and what do we end up with? Something very much not list. In this case a number. It's interesting to note that the reduction here when it's a full reduction like that it's not really chainable with the other functional programming things is it?
[00:06:32] Here's where we start to see the nit picks that functional programmers have because now we don't have. Now we have a breakdown in the consistency of our compositional pattern because if you needed to do some other mathematic operation like a map on this and the end result was a number, you can't just call map directly off of it.
[00:06:51] You're gonna have to take that and put it into an array and then call map on the array or something. It's just some weird inconsistency. Usually your reduction, especially if it's a full one, usually that's the last step in your chain anyway. So it's usually not a big deal.
[00:07:04] But there have been places where I've had this issue. So the other way to think about it, I don't have a code snippet for this, but it is possible that a reduce is not a full reduce. In other words, the end result of your reduction might just be another list.
>> Kyle: An example, we're gonna come back to this later, but while you're thinking about it, an example, what if I wanted to perform a unique operation on a list? What if I wanted to filter out the list and say, this list needs to only have unique values? I need to turn it from a list into a set, basically.
[00:07:39] Well, we couldn't use filter. Why couldn't we use filter?
>> Kyle: What was the one characteristic I said about how filter gets used to solve problems? Remember, filter can only make its decision based upon the value itself. That's the appropriate way of using it. There's ways to bastardize it, of course, but that's the appropriate way of doing it, is making a decision based on the value itself.
[00:08:07] Well, we can't unique an array based on knowing the value itself, can we? We need something more context. So, actually, uniquing could be implemented more effectively. And more traditionally with the reduce, because what we could do is, our running accumulator could be an empty list. The initial value could be an empty list, and we could stick something in the list only if it's not already in the list.
[00:08:28] So we could run through the entire original list looking at values. And every time we're about to put one into the new list we ask, is it not in the list yet, is it not in the set yet. That's an example of uniquing. So that's what I would call a partial reduction.
[00:08:43] Because the end result is still the list. But it's not a one to one mapping. It wasn't a filtering based upon one individual value, it was something more complex. That's what I meant earlier when I said reduce is way more complex. More powerful but there's also a bunch of ways that you can use it in very impure ways.
[00:09:00] So, you have to be careful about that.