This course has been updated! We now recommend you take the Functional-Light JavaScript, v3 course.
Transcript from the "Filter: Exclusion" Lesson
[00:00:00]
>> Kyle: Okay, our next operation we wanna look at is the filter operation. Filter is, I'm gonna argue, fundamentally an exclusion operation. And I need to provide some nuance to that concept of exclusion, because in the real world, we think of and define, if you read the dictionary, we think of filtering primarily as an exclusionary activity.
[00:00:26] That's the way our brains typically think. But when we go to implement it in programming, for some reason which I've never been able to figure out, we historically have always implemented filtering as an inclusionary activity. So I wanna talk just a moment about the philosophical difference between filter being an exclusionary versus an inclusionary.
[00:00:48] Let's imagine that I'm cooking and the only thing that I cook in my household Is spaghetti. My wife does all the other cooking, but I like to do spaghetti. And even then, I only do it some of the time. She's the cook of the family. But I can make some pretty good spaghetti.
[00:01:04] When you're cooking up a pot of spaghetti, you put it in a pot full of water and you're cooking up your spaghetti. And then you're ready. They're cooked. The noodles are cooked and you're ready to get them out. What you generally do or at least this is what how I was taught, is you pour that entire contents of that pot into or through a strainer, a filter and the water runs in to the sink and what's left is your noodles.
[00:01:32] So my question to you is in that process of pouring something through a filter, would you be more inclined to describe that as I filtered in the noodles or I filtered out the water? These are hypotheticals. I'm not expecting answers. I just want you to think about it.
[00:01:48] These are rhetorical questions. When you pour the noodles out, are you filtering in the noodles or filtering out the water? I think most people probably tend to say I'm filtering out the water. Same thing with coffee. When you put a coffee filter in the coffee machine. I know that's like sacrilege.
[00:02:03] We're not supposed to use coffee filters, right? But if you put a coffee filter in the coffee machine and that water comes through it, are you filtering out the coffee beans or are you filtering in the coffeed water? Kind of depends on how you look at it. If I go to the grocery store and there's a big stack of apples and oranges all mixed together and I love apples, I want all their apples but I don't want any of their oranges.
[00:02:30] So I have my shopping basket there and I sift through all of those fruits. And I pick out and put the apples in my basket and I leave the oranges behind or I throw them in the trash can, cuz I don't like oranges, whatever, have I filtered in the apples or have I filtered out the oranges?
[00:02:50] And my final example, if you start with a list of numbers one, two, three, four, five and you end up with a list of one, three, five. The odds. Have you filtered in the odds or have you filtered out the evens? In most of those cases if you dig into it, I think you'll agree it's a little bit more natural to think of it as an exclusionary activity.
[00:03:14] However, when we implement filter in programming, you return true to say I wanna keep it. That's kind of a semantic signal that this is an exclusionary thing. I'm returning true to keep it and returning false to discard it or get rid of it. So, filter looks like this.
[00:03:33] We start out with a list and we make some determination with each value in the list. And if we wanna keep it, we put it in the new array, and if we don't wanna keep it, we filter it out, and leave it out of the new array. So, filter, where map always was gonna start with a list of some length and always result in a list or data structure of the same size and shape.
[00:03:58] Filter is designed to create a new list that's no bigger than and oftentimes gonna be shorter than the original list. Not always. You could do a filter that resulted in all the items passing, but generally speaking, the new list is gonna be shorter than the old one.
>> Kyle: So we can implement an exclude utility, aka a filter utility.
[00:04:23] This is very similar to what we did with map. We're gonna loop through the original list adding items to a new list, but before we do we're gonna call a function to test the value. And there's a special name for a function that returns a true or false.
[00:04:38] We talked about this early in the course. They're called predicate functions. So we're calling a predicate function with each value in the list, and if it gives us true, we add it to the new list, and if it gives us false, we filter it out and throw it away.
[00:04:53]
>> Kyle: So down at the bottom, when I call exclude on the list of [1,2,3,4,5] with the isOdd predicate, I end up with a list of odd numbers. So, here's the real pain point from a semantic perspective. I call that function isOdd because if you passed in a single number to the isOdd function like the number three, you would expect for that function to give you true if it was in fact odd which it is, right?
[00:05:25] So that's the right name for that predicate. We should call it isOdd. If we called it isEven, that would be deliberately screwing with people. You following? But now we look at using it in this list operation which we've called exclude, because that's what people think when they think of the word filter.
[00:05:44] And we are excluding the odd numbers into the list. Do you see why that utility actually ought to be called include if that's the semantic that we're creating? What I've done is decided in my own programming that if I'm given a filter utility that acts like an inclusionary activity, I alias that utility to be called filter in.
[00:06:10] And then I make another utility called filter out that does the exclusionary activity. So now I have a filter in and a filter out, and it's super clear in my code what thing I'm doing there. I don't have this mismatch of semantic. Where I'm excluding an inclusionary sort of test.
[00:06:31] So I don't like this method, the one that's provided on arrays, cuz it's called filter, but it's actually should be called filter in because it's an inclusionary activity.
>> Speaker 2: There's a question online. Just to clarify, we have not mutated the original array?
>> Kyle: Yeah. Correct. We have not mutated the original array in any of these examples.
[00:06:53] In all of these examples, so far we've looked at map and filter. In both of those case, a new list is the result. We aren't mutating the existing list. We're creating a new list. That's consistent with everything that we've talked about up to this point about immutable data and treating values as immutable regardless of whether they are.
[00:07:11] Even the built-in array methods, thankfully, do that exact same thing. The filter method here returns us a brand new array, not mutating the existing array in place. Be careful because the existing array methods, the ones that are built into JavaScript, only some of them that is true of.
[00:07:30] Map, filter, we're gonna look at reduce next, they all behave correctly. But other ones like, for example, sort and reverse, those ones are mutators. They mutate in place. So that can be quite frustrating. One other example of one that does behave correctly is the concat method that instead of mutating the array to add something on the end like push does, concat creates a whole new list with new the items added on.
[00:07:58] So concat can be useful, and by the way that's a hint for the upcoming exercise.