JS/TS arrays - Getting a handle on the filter function

I think it is fair to say that functional programming has bounced back in a big way over the past number of years and it does not look like it is going anywhere, this holds true for Typescript as well.

If I was a betting man, I would say that Typescript/Javascript is the most used language out there and whether you are mostly a back end java developer or a die-hard Angular developer I am willing to bet that if you haven't seen the filter function in action you will be thanking yourself for reading this!

Those that come from a functional background will probably already have a very good idea on how this function works but let's go through some examples, hopefully, at the end of this post, you will have an appreciation for how powerful this function is.

Array: filter()

The filter function does exactly what it says on the box, it filters elements out of an array, let's get some examples out there:

1| const numbersArray: number[] = [1,2,3,4,5,6];
2| const aboveFour = numbersArray.filter(ele => ele > 4);
3| console.log(aboveFour); // 5 6 

Whilst this is a very simple example it should instantly be clear what filter does, we pass through a predicate function that is applied to every element in the array, if the predicate, when applied to the value returns true that element is added to the new array.

Note how I stated new array, the filter function does not mutate the existing array it returns a completely new array which we assign to the aboveFour variable on line 2.

Now we could decide to use the aboveFour variable and apply another filter to it but as filter returns a new array we should be able to chain filters, correction; we can chain filter functions.

Here is an example where we filter for all numbers above 4 and then filter that array out for numbers below 6:

1| const numbersArray: number[] = [1,2,3,4,5,6];
2|    const aboveFour = numbersArray
3|      .filter(ele => ele > 4)
4|      .filter(ele => ele < 6);
5| console.log(aboveFour); // 5

Why do this though? Would it not be better to simply apply both filters into one filter, well in this case maybe but we could go one step further and be so much more expressive, we could do this:

1| const numbersArray: number[] = [1,2,3,4,5,6];
2|    const aboveFour = numbersArray
3|      .filter(forMoreThanFour)
4|      .filter(forLessThanSix);
5| console.log(aboveFour); // 5
6| const forMoreThanFour = (n: number) => n > 4
7| const forLessThanSix = (n: number) => n <6

Bear in mind this is very simple, normally we would be expecting to filter more complicated objects or have more complicated business logic.

With that in mind look at how easy this is to read, look at how easy those functions are to test in isolation if we replace the simple business logic with more complicated business logic we should still end up with simple, split up predicates that are pure functions and easily testable.

Sadly all good things must come to an end, you should be aware that chaining filters in this way will cause 2 iterations of arrays, the silver lining here is if you order your filters in such a way that you reduce the size of your array as early as possible this will be less of an issue.

This is not a reason not to chain functions in this way! When talking about performance it is easy to make a mountain out of a molehill, be objective about this, I would argue that if this costs you an extra ms then it's probably worth it!

Now of course if your application needs to run on mobile devices you might have some cause for alarm if you are dealing with an array of thousands of objects but let's take a look at an example:

 1|   const numbersArray = Array.apply(null, {length: 10000}).map(Number.call, Number)
 2|   const moreThanFour = ele => ele > 4;
 3|   const lessThanSix = ele => ele < 6;
 4| 
 5|   const result = numbersArray
 6|     .filter(moreThanFour)
 7|     .filter(lessThanSix);
 8|   console.log(result);
    
In this example I create an array of length 10000, I have to iterate over this twice due to the filters, give it a go, do you see a measurable performance issue? Perhaps for your use case you do and have serious concerns, that is fine! My point is to look at performance with full context.

Back to the example, notice the order of the filters, in this example, if we have this kind of data it would make sense to switch these filters around, this way we filter out all numbers above 6 and the second iteration only has to iterate over a small array; granted this is not always possible but if you like this way of coding then these are things you should consider, the overall takeaway here is we can chain filter, map and reduce and in some cases it is appropriate to do so, in others, not so much.

So far I have shown examples of using the filter but let's step back and think about the alternatives, most of you will probably be thinking of the for loop or even the foreach loop, there is no denying these can achieve the same thing but what is it we are getting from choosing filter over these solutions?

To answer that, take another look at the examples, would you say these are much more expressive in what they do? I certainly would, this is probably the most fundamental distinction of the filter function against a for-based solution or while-based solution.

When using for and while to filter you not only have to declare what you want to be filtered but also how you filter; you have to provide a way for the for loop to iterate whereas with the filter function it already knows the how leaving you to focus on the what.

Hopefully, from the past two paragraphs, you will agree that the filter function highlights the benefits of a declarative approach to programming, a declarative approach is more expressive and as it does not expose how only the what you tend to get a lot of readable and concise code.

Finally, I invite you to think of SQL, SQL is a declarative language, you declare what you want your result to be, you trust the engine to efficiently get you the results you require, for the most part, this trust is normally well placed if you apply the language correctly.

Filter, map, reduce and other such functions are, I would imagine implemented and tested rigorously to be as efficient as possible so why not let JavaScript do what MYSQL does for you: allow you to think about the what and not the how.

If you favour this declarative, functional approach over the usual imperative approach I am willing to bet you will start to find much more readable, simple and code that is much easier to test.

If this has a peaked your interest then I invite you to take a look at my posts about RxJs and other functional approaches that you can apply to your Javascript/Typescript code.

Comments

Popular posts from this blog

Angular material table with angular 7 - Defining columns using ngFor

Creating contextual drop downs using RxJs, Angular 7 and Angular Material

From nothing to an Angular 7 app running in 5 minutes with AWS