paint-brush
Meet the Holy Trinity of Functional Programming: Map, Filter, and Reduceby@mlevkov
733 reads
733 reads

Meet the Holy Trinity of Functional Programming: Map, Filter, and Reduce

by Mikhael LevkovskyAugust 18th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

For loops are for suckers (well not really), higher order functions are all the rage and you want to understand what this means. Map, Filter and Reduce are the 3 most popular methods. It's important to understand how and when to use these methods, but more importantly, you also need to know when to avoid them. The map function goes through every single element of an array and applies a passed in function to each element. If you have a huge collection that takes up a lot of memory, maybe using a good old fashion for loop is better.

Company Mentioned

Mention Thumbnail
featured image - Meet the Holy Trinity of Functional Programming: Map, Filter, and Reduce
Mikhael Levkovsky HackerNoon profile picture

You've watched the Youtube videos, you've done the tutorials on Pluralsight and you consider yourself a Javascript expert.

However, now you want to hang with all of the cool kids and talk about functional programming. For loops are for suckers (well not really), higher order functions are all the rage and you want to understand what this means. Well the very first step is understanding the 3 most popular methods which are: Map, Filter and Reduce.

It's important to understand how and when to use these methods, but more importantly, you also need to know when to avoid them.

To setup some context, let's pretend we have an application that saves basic user information. Let's assume our User entity has the following properties: id, firstName, lastName, date of birth, email, an avatar url, a username and a flag to tell us if they are active or not.

User.ts

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  public id?: number;
  @Column()
  public firstName: string;
  @Column()
  public lastName: string;
  @Column()
  public age: number;
  @Column()
  public email: string;
  @Column()
  public avatarUrl: string;
  @Column()
  public username: string;
  @Column()
  public isActive: boolean;
}

If the annotations are not familiar don't worry about it. It's not important for the example and is just some TypeORM specific annotations.

To explore the concepts of map, filter and reduce, we will create a class with the following methods:

1. standardizeAvatars 👉 Accepts an array of users, and adds a base url to all of the avatar urls

2. getActiveUsers 👉 Accepts an array of users, and returns only the active ones.

3. getAllAges 👉 Accepts an array of users, and gets the total age of everyone in the array.

pssst I tweet about code stuff all the time. If you have questions about how to level up your dev skills give me a follow @mlevkov

Map 🗺

The .map function goes through every single element of an array and applies a passed in function to each element. There is one VERY VERY important point to pay attention to that's not talked about is that the .map function goes through EVERY 👏 SINGLE 👏 ELEMENT 👏 and returns a brand new array with the modified values.

That means you can't break out of the loop AND you will duplicate every single element of the array. If you have a huge collection that takes up a lot of memory, maybe using a good old fashion for loop is better.

Here is an example of how to convert a for loop to .map.

/**
   * Assumes that the urls will be hosted on imgur.
   * Appends the base imgur url to each avatar if it's set or sets a default one
   * if it isn't.
   */
  public standardizeAvatars(users: User[]): User[] {
    // using a for loop
    for (const user of users) {
      let url = 'https://imgur.com/avatars/default.jpg';
      if (user.avatarUrl.trim()) {
        url = `https://imgur.com/avatars/${user.avatarUrl}`;
      }
      user.avatarUrl = url;
    }

    return users;
  }
 /**
   * Assumes that the urls will be hosted on imgur.
   * Appends the base imgur url to each avatar if it's set or sets a default one
   * if it isn't.
   */
  public standardizeAvatars(users: User[]): User[] {
    // using map
    return users.map((user: User) => {
      let url = 'https://imgur.com/avatars/default.jpg';
      if (user.avatarUrl.trim()) {
        url = `https://imgur.com/avatars/${user.avatarUrl}`;
      }
      user.avatarUrl = url;

      return user;
    });
  }

Filter 🥅

The .filter function goes through every single element of an array and checks if that element returns true or false when passed into the passed in function. If it returns true, we keep that element, otherwise we don't. Phew that's a mouthful. Don't worry there's an example below.

Just like .map, the .filter function goes through EVERY 👏 SINGLE 👏 ELEMENT 👏 and returns a new array with just the elements that didn't get filtered out. So while your collection might shrink in size (or might not), you will still go through every single element without the option to break or continue. If you are doing some heavy computation then on every element that you want to keep, then consider using a for loop.

Here is an example of how to use .filter and the same example with a for loop.

  /**
   * Goes through the whole array and returns a collection of only active users.
   */
  public getActiveUsers(users: User[]): User[] {
    //for loop
    const result: User[] = [];
    for (const user of users) {
      if (user.isActive) {
        result.push(user);
      }
    }

    return users;
  }
 /**
   * Goes through the whole array and returns a collection of only active users.
   */
  public getActiveUsers(users: User[]): User[] {
    return users.filter((user: User) => user.isActive);
  }

Reduce 👨‍👦

The .reduce function will also go through every single element of an array BUT in this case it will not return you another collection, but a single element. In fact, you can say it "reduces" your collection to one item.

The .reduce function accepts a custom function that you make and a placeholder value that keeps track of the operations. This will guarantee that your collection will shrink in size.

/**
   * Goes through all the users in the array, sums up their
   * ages and returns the final result;
   */
  public getAllAges(users: User[]): number {
    let result = 0;

    for (const user of users) {
      if (+user.age) { // <- the + is neat shortcut to check if you have a number
        result += user.age;
      }
    }

    return result;
  }
/**
   * Goes through all the users in the array, sums up their
   * ages and returns the final result;
   */
  public getAllAges(users: User[]): number {
    //sum keeps track of the ages, the 0 is the starting point
    return users.reduce((sum, user) => sum + user.age, 0);
  }

There you have it, how to use the holy trinity of functional programming.

If you want to level up your coding skills, I'm putting together a playbook that includes:

1. 30+ common code smells & how to fix them

2. 15+ design pattern practices & how to apply them

3. 20+ common JS bugs & how to prevent them

Get early access to the Javascript Playbook 🚀