This course has been updated! We now recommend you take the Functional-Light JavaScript, v3 course.
Transcript from the "Exercise 4 Solution Part 3" Lesson
[00:00:00]
>> [MUSIC]
[00:00:03]
>> Kyle Simpson: Either iteratively or recursively this problem can be solved. Finally, let's try to solve it, with map and reduce, okay? So, with map and reduce what we can observe is that the calculation, or the changing of a value to a function is a transformation, is it not? So I can take arr, and simply call map with foo as my function.
[00:00:32] That's going to make sure to wrap all of my values into function. And now, I want to perform a composition of all those functions. Does everybody see that? How do I do a composition?
>> Speaker 2: Reduce.
>> Kyle Simpson: Right, what does reduce take? What's it's first parameter. It's first argument.
[00:00:59]
>> Speaker 3: An array?
>> Kyle Simpson: It takes a predicate function because remember it's operating on the array prototype method. So we don't have to pass the array in. Its going to get that context already. So, the first argument to the dot reduce is going to be the predicate function. The function that is going to do the work.
[00:01:15] So we'll define that function. We know that functions going to get two arguments. I'm just going to temporarily call them X and Y, but we're going to rename the. So, let's do our function there. And then what else does it get? It gets an initial value. What initial value should we use here?
[00:01:30] Well, there's a couple of different ways to think about this. We are doing a map across the entire list, right? So, that's one way of doing it. But, the other way of doing it is to slice off.
>> Kyle Simpson: Let me just double check and make sure I'm not, I'm trying to keep close to, my brain works differently when I solve this dilemma.
[00:01:56] I'm trying to make sure to not get too off track of your solution file.
>> Kyle Simpson: Okay. So, we could do slice(1) because we want to only perform the reduce transformation across the second half of the list. We have the first item, and we want to deal with the first item.
[00:02:20] And then, whatever happened, we're going to reduce across the second list. So, if we did it that way, what's our good initial value?
>> Speaker 2: Array zero.
>> Kyle Simpson: Arr
[0]. You see how I did that? I took the first element to the list and made it my initial value, and did a reduction against the rest of the list.
[00:02:40] That's just the shortest way of doing it. That may not always be the way you want to do it. You might have just started it out with a good initial value like a function that returns zero. So, you could have said foo zero here. Okay. But now we want to ask, what is our accumulation?
[00:02:54] And, the typical names for these, as I said, the typical names in the reduce predicate are accumulator ACC and current. That's how most people will call them. I don't love accumulator and current so I'm going to actually use previous and current. Because I think previous is a little easier to understand than accumulator.
[00:03:22] But, let's analyze what is going to happen with this first call. With this very first call what's going to happen is that I'm going to, let's just for simplicity sake go ahead and pass in these as functions, so that I'm not having to do that map there. Of course we know we could do the map.
[00:03:41] Let's just for simplicity's sake pass those in.
>> Kyle Simpson: Okay, so array of 0 is what?
>> Speaker 2: 10,4,10. Or a function now.
>> Kyle Simpson: It's a function, right? And, curr is going to be array in the one position which is also a function, right? So, I have two functions. How am I going to compose these two functions together?
[00:04:13] I'm going to create a new function. That's one of the most common ways that we compose functions is to wrap the two of them into one bigger function. I'm going to take create a new function and I'm returning it, which is then going to make it be the next previous.
[00:04:30] So, what is this function going to do? It's going to actually call add2, with previous and cur. So, we're not calculating that sum now. We're just making a bigger and bigger wrapped function. The end result of our reduction here, is not to have a sum. It's to have one giant function that when we call that function, it's going to calculate all those intermediate sums and put them all together, making a bigger and bigger function.
[00:05:01] The first time, we make one function and we replace the first two elements in our array with one function that's set when it's called, it will calculate the sum of those two functions. The second iteration of our reduction, we take that composed function plus the function in a list, we compose those two together.
[00:05:21] The third iteration we take a bigger composed and the third item in the list and we compose those. Until we get to the very end, we have one giant function. So, if I just simply said return arr dot slice, we would not have what we want here in the sense that we would not have calculated the sum yet.
[00:05:42] We simply would have produced a function. So, what do we do? We then call that function. And, that will call it, and call it, and call it, and call it however down it needs to go until we end up with our actual sum result. Yes.
>> Speaker 4: A couple of questions here.
[00:06:03] Is it possible to leave off the initial value and reduce?
>> Kyle Simpson: If you leave off the initial value it's a little more complicated. It will default to undefined, which might be an okay initial value depending upon what you're doing, that might be an okay initial value. But, you should also be aware that if you leave off the initial value and your list has one or fewer items Items that throws an error.
[00:06:33] So, it's not really ever a good idea to leave off the initial value. You almost always want to give it some useful initial value.
>> Speaker 4: He's wondering if you maybe can explain again, removing the first value. And why are we doing the original slice, I guess?
>> Kyle Simpson: The reason we do the slice(1) here is because what we want is to perform a reduction where we have two items.
[00:07:04] And, we could have not done a slice at all. And then, we could have had an initial value here of foo(0). If I did foo(0), does everybody see that that would've been a suitable initial value? It would've been a function that when called, just simply returns 0. Which is going to have a no-op.
[00:07:24] So, I could have done my solution like that.
>> Kyle Simpson: Operator reduction across the entire list where my initial value is simply a basically a no-op function. That's one way of doing it.
>> Speaker 3: Why is the other one preferable?
>> Kyle Simpson: One less function call wrapping layer.
>> Kyle Simpson: So, all I was trying to do, I've already shown you starting out with an initial value, here I'm saying actually we already know that array of zero is a suitable initial value, which is why we put array of zero there and we do the reduction across the rest of the list.
[00:08:04] Either way is entirely valid.
>> Kyle Simpson: It's important to keep track. I know that we're, that you kind of get into this nesting thing and it's hard to keep track of the reduce predicate. That's this one right here. It returns a function. So every time we pass in two functions, we get a third function out that's a composition of those two functions.
[00:08:34] And then, when we have another function and another function, and this one is already a composition, we get a super composition of a composed function with a regular function. And then, over here on the fourth item we get a super super composed function with a regular function. And, we've just made functions wrapping together up the list.
[00:08:52] So, if we were to unroll all of that, if you were to try and print out this thing, and we passed down a list of four items, we'd have four levels of function call. This function returns this function call, returns this function call, returns this function call, returns the add2 of two values.
[00:09:08] And, it would just simply call all those functions and unwrap them. So, this isn't recursive. This is simply unrolling recursion into a call stack as many levels deep as you need. And, just as a little side trivia note, has anyone ever heard of the Y Combinator? The Y Combinator is a general pattern for doing exactly that.
[00:09:30] I don't fully understand it, but I know that the Y Combinator takes recursion and unrolls it to a progressively wrapped function loop. So, we could have solve our problem exactly like we did here. We could have solved it with the Y Combinator and got the exact same result.
[00:09:47] We would have the exact same set of functions wrapped in other functions, wrapped in other functions. That's how Y Combinator gets around recursion, or gets around the problem with recursion is allocating all that new stack depth. The Y combinator gets around it by doing fixed proper tail call function calls inside of function calls, inside of function calls.
[00:10:21]
>> Kyle Simpson: So, we make sure to call that function, and we're going to end up. So, let's just double check this, this is my EX4.js. I'm going to, actually let me just copy it in the console. You can run it in your browser using the html file if you'd like.
[00:10:35] I'm just going to run it in the browser, make sure I didn't miss anything. I forgot to point out there was supposed to be passing in an array. By the way, ES6, how do I get it back to where I do want to pass them in as individual ones instead of as an array?
[00:10:55] Could have just done the ..., that's all I needed to do there. So, if you want to have a call signature where you don't pass in an array, just declare your call signature as basically gathering together all arguments into an array for you. All right, let's try it again, see if I didn't mess something up.
[00:11:10] And there we get the 181.