Union types, pattern matching and exhaustiveness in today's JavaScript

Pattern matching and union types are, in my view, one of the concepts in programming which don't get the attention they deserve.

In this post, I'm going to show how they can help us write more declarative code which, oftentimes, is much easier to comprehend than purely imperative code.


Just by looking at declarative code we find out what's happening much faster

These ideas will change the way you think about designing the code of your next project and show how declarative code can make it much easier to maintain.

Union types are readily available to you in TypeScript

If you work with TypeScript today, you may already be familiar with union types.

They make it trivial to express a value could belong to one set of values or another,  e.g. a status could be any string or null. This guarantees we cannot assign a number or boolean to a variable of this type.

type Status = string | null;

Even better, we can restrict possible choices to a subset of a wider set of values, i.e. a status of could either be string "Done"  or "Pending" but no other strings should be allowed.

type Status = 'Pending' | 'Done';

This helps prevent mistakes such as typos, e.g. "Dnoe" vs "Done".

When used well, union types help provide extra type safety around the code by narrowing down what can be done without getting a warning or an error from the compiler, and thus, their use can limit the amount of human errors to minimum.

const status: Status = 'Dnoe'; // <- typo
// => Type '"Dnoe"' is not assignable to type 'Status'

const status: Status = 'Done';
// => OK

Union Types in JavaScript

Union Types are common amongst many functional programming languages. Some call them unions, sum types, variants, disjoint unions, tagged unions. They can be found in Haskell, PureScript, OCaml, Reason, ReScript, F# and Elm.

Some non-functional languages adopted them as well. Some treat union types as first-class citizen in the language's syntax (e.g. Rust, TypeScript). Native support in the language isn't the only way of adopting union types.

Functional programming enthusiasts in the JavaScript ecosystem already created some libraries readily available to install through npm.

Daggy is one of my favourites. It's small and very easy to use:

const daggy = require('daggy');

const Status = daggy.taggedSum('Status', {
  Pending: [],
  Done: [],
});

The above example does a couple of things:

  • It defines a union type, we name it Status
  • It defines two constructors, Pending and Done, accepting no values

Now, let's explore how we could work with the new data type. We could start with an array of jobs:

const jobs = [
  { id: 1, status: Status.Pending },
  { id: 2, status: Status.Done },
  { id: 3, status: Status.Dnoe }, // <- notice the typo
]

So far so good, except, there was no type checker or compiler to warn us about the wrong status used in the last example. We'll soon learn how to deal with these types of errors.

Evolving Design

Requirements change and so does code. At some point we decide to extend the Status type with one extra constructor, Failed, signalling failure.

const Status = daggy.taggedSum('Status', {
  Pending: [],
  Done: [],
+ Failed: ['reason'],
});

The Failed  variant will be ideal for these jobs with invalid statuses. It will accept a parameter, reason, indicating the cause of failure.

function normalize(job) {
  if (!Status.is(job.status)) {
    return { ...job, status: Status.Failed('Invalid job status') };
  }

  return job;
}

Given a job with a wrong status, e.g. Dnoe, we return a copy of that same job with a new status  Status.Failed('Invalid job status').

Now, are there any places where we didn't cater for the new variant?

Pattern Matching

A lot of functional programming languages, especially these in the ML-family of languages (e.g. OCaml, Haskell, and their derivatives), have a very useful tool in their toolbox, pattern matching.

Pattern matching, in a declarative way, allows to structurally match against every possible value or a combination of values in our data structure, and define a desired behaviour for each.

With proper type inference, some languages can quickly detect uncovered cases and warn the programmer about patterns which are not exhaustive, e.g. because a new constructor was introduced, or perhaps, the programmer didn't think about a possible edge case.

Pattern matching in Daggy

While we wait for the pattern matching proposal to possibly become a standard in JavaScript one day, its minimalistic variation has already been implemented in Daggy.

We pattern match in Daggy with cata method. Let's see how we can make use of it in a function which executes jobs.

function execute(job) {  
  return job.status.cata({
    Pending: () => {
      console.log(`Executing job#${job.id}..`);

      return { ...job, status: Status.Done };
    },
    Done: () => job,
  });
}

Daggy detects we missed the Failed constructor and screams at us:

"TypeError: Constructors given to cata didn't include: Failed"

From now on, as the design of our code evolves, we will be able to detect all non-exhaustive pattern matches and tackle them on a case-by-case basis:

function execute(job) {  
  return job.status.cata({
    Pending: () => {
      console.log(`Executing job#${job.id}..`);

      return { ...job, status: Status.Done };
    },
    Done: () => job,
+   Failed: (reason) => {
+     console.log(`Job#${job.id} failed with "${reason}".`);
+
+     return job;
+   },
  });
}

Complete code listing

A fully interactive version of the code from this post can be found under https://codesandbox.io/s/union-types-in-javascript-today-jrb52

const daggy = require('daggy');

const Status = daggy.taggedSum('Status', {
  Pending: [],
  Done: [],
  Failed: ['reason'],
});

const jobs = [
  { id: 1, status: Status.Pending },
  { id: 2, status: Status.Done },
  { id: 3, status: Status.Dnoe }, // <- notice the typo
];

function normalize(job) {
  if (!Status.is(job.status)) {
    return { ...job, status: Status.Failed('Invalid job status') };
  }

  return job;
}

function execute(job) {
  return job.status.cata({
    Pending: () => {
      console.log(`Executing job#${job.id}..`);

      return { ...job, status: Status.Done };
    },
    Done: () => job,
    Failed: (reason) => {
      console.log(`Job#${job.id} failed with "${reason}".`);
 
      return job;
    },
  });
}


jobs.map(normalize).forEach(execute);
// Executing job#1..
// Job#3 failed with "Invalid job status"

Where can it be useful?

Pattern matching and union types are brilliant ideas and yet, they don't get the attention they deserve.

When incorporated in our code, they help us:

  • make code easier to reason about by enforcing declarative-style
  • improve type-safety by warning about non-exhaustive pattern matches
  • change the way we approach coding by forcing us to think about code design first

Think about it, we started by defining the core language of our application. We did that in stages, the language evolved over time:

  1. a job
  2. a job with a status
  3. a job with a status which could either be done or pending
  4. ... and so on

At that stage, implementation details didn't matter that much, did they?

Potential application of union types

There is a lot of potential use cases for union types. They could be practically used in any project. I'm sharing some two interesting examples below.

1. React

A good use case for union types and pattern matching could be declaring possible states of a React component with Daggy, and, later on, using pattern matching to define what the component should render:

const daggy = require('daggy');

const State = daggy.taggedSum('State', {
  NotRequested: [],
  Loading: [],
  Loaded: ['data'],
  Error: ['error'],
});


const App = ({ state }) => {
  return state.cata({
    NotRequested: () => <NotRequested />,
    Loading: () => <Loading />,
    Loaded: (data) => <Loaded data={data} />,
    Error: (error) => <Error error={error} />,
  })
}


<App state={State.NotRequested} />
<App state={State.Loading} />
<App state={State.Loaded(['item 1', 'item 2'])} />
<App state={State.Error('5xx Internal Server Error')} />

2. Domain Specific Languages (DSLs)

Another use case might be designing a custom domain specific language which clearly defines the domain of the thing we're building. We can later on compose an application from these building blocks.

Here's a definition of an abstract syntax tree of a tiny language embedded into JavaScript, and an interpreter for it:

const daggy = require('daggy');

const Expression = daggy.taggedSum('Expression', {
  Number: ['value'],
  Add: ['left', 'right'],
  Subtract: ['left', 'right'],
});

const evaluate = (expression) => {
  return expression.cata({
    Number: (value) => value,
    Add: (left, right) => evaluate(left) + evaluate(right),
    Subtract: (left, right) => evaluate(left) - evaluate(right),
  });
};

const addExpression = 
  Expression.Add(
    Expression.Number(1), 
    Expression.Number(2)
  );
  
evaluate(addExpression); // => 3


What do you think about these ideas?

Please let me know your thoughts on twitter at @maciejsmolinski.

JavaScript Series: Context in JavaScript Functions

Have you ever noticed this keyword in JavaScript and wondered how it works? Especially in a context of a function - where does its value come from? How can one find it useful?

Calling a simple function

First, let’s bring back our favourite fullName function:


  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }
 

We’ve learned how to execute this function with some parameters already:


  fullName({ firstName: 'Jason', lastName: 'Bourne' });
  // => 'Jason Bourne'

Not scary, right? Is there any other way to call functions? Turns out there is!

Functions can operate with context

Before we move to the more advanced parts, let’s learn how we could get the same results back by calling this function differently:


  fullName.call(null, { firstName: 'Jason', lastName: 'Bourne' });
  // => 'Jason Bourne'


  fullName.apply(null, [{ firstName: 'Jason', lastName: 'Bourne' }]);
  // => 'Jason Bourne'


  fullName.bind(null)({ firstName: 'Jason', lastName: 'Bourne' });
  // => 'Jason Bourne'

Three different ways and same results. Why bother?

Turns out functions can operate with context. We refer to the context via this keyword. In our example, we intentionally ignored the context parameter and used null.

By context, we usually mean that a function is part of something bigger. In such cases, this refers to that bigger thing. Let’s bring an example:


  var person = {
    firstName: 'Jason',
    lastName: 'Bourne',
    fullName: function () {
      return this.firstName + ' ' + this.lastName;
    }
  };

What you see above is a simple object with personal data. You might have noticed already we didn’t use any parameters in the fullName function and I can assure you it still works!


  person.fullName();
  // => 'Jason Bourne' 

How come? Our function knows exactly it is part of the object and easily recognised this refers to the whole person.

Be careful!

While this example works perfectly fine, if we wanted to borrow that function and use it somewhere else, we might lose the context! See:


  // What if I wanted to save a reference to the function
  // and refer it by the other name somewhere else?
  var borrowedFullName = person.fullName;


  borrowedFullName();
  // => 'undefined undefined'
  

Ooops! Something bad happened. Turns out your function is smart enough and looks for something it could attach to. It totally forgot about the person and tried to check what the current context has to offer. Since your global object, usually* window, does not have fullName nor lastName assigned, fullName returned undefined undefined.

Make sure you don’t harm yourself

First of all, functions operating on context should be used carefully. It is a good practice to trigger strict mode in your functions to make sure you don’t accidentally change global variables. here’s how you do it:


  var strictPerson = {
    firstName: 'Jason',
    lastName: 'Bourne',
    fullName: function () {
      'use strict';
      return this.firstName + ' ' + this.lastName;
    }
  };

Besides the variable name, you may notice ‘use strict’ string in your function. This is all it takes to trigger strict mode in your browser. Now the browser will complain when executing borrowed function:

  
  // Borrowed function with strict mode
  var borrowedStrictFullName = strictPerson.fullName;

 
  borrowedStrictFullName();
  // =>
  // TypeError: Cannot read property 'firstName' of undefined
  // at fullName (...)

I can assure you this is good! You’re not going to hurt yourself by accidentally modifying global variables.

Force a context when calling a function

Good, we learned how to make our code a little bit safer. Now it’s time to move on to contexts! Remember how borrowedStrictFullName call threw an error in strict mode? Right, strict mode prevented us from modifying global variables. Does it mean this function is useless? No, it doesn’t! We can use it. Let’s call it with some context!


  borrowedStrictFullName.call({
    firstName: 'Marie',
    lastName: 'Kreutz'
  });
  // => 'Marie Kreutz'

It worked! Our parameter-less function worked with a context we enforced! Are there any alternatives?

  
  borrowedStrictFullName.apply({
    firstName: 'Marie',
    lastName: 'Kreutz'
  });
  // => 'Marie Kreutz'

"I’m confused" you may say. Same results, what’s the point of having two functions that do the very same thing?

The difference is subtle and it does not necessarily relate to context binding. It is the way these functions treat parameters! I’ll give you an example:


  /**
   * Function that adds two numbers:
   * 
   *   add(1, 2); // => 3
   *   add(5, 1); // => 6
   * 
   * No context involved
   */
  function add(a, b) {
    return a + b;
  }

Below you can find the difference between the way call and apply pass parameters to the function:

  
  add(1, 2);
  // => 3
  
  add.call(null, 1, 2);
  // => 3

  add.apply(null, [1, 2]);
  // => 3

As you can see, call takes an undefined number of parameters and invokes the function as you would do it manually. It just takes one extra context parameter that wasn’t really useful in our case.

The difference is that apply takes only two parameters. The second one is an array of parameters that you would normally pass to the function one by one.

"Why does it matter?" you may wonder.

To put it simply, you’re going to use call mostly to invoke a function with a context when you know exactly how many parameters the target function expects and you’ve got these parameters ready to be used.

On the other hand, apply comes in handy when your function accepts any number of parameters or the number of parameters you pass to the function may vary. Here’s a real world use case:


  /**
   * Math.max takes any number of integers
   * and returns the biggest one
   * 
   * You cannot pass an array of integers to it though!
   * It would return a NaN!
   */
  Math.max(1,2,3,5,4); 
  // => 5

  Math.max([1,2,3,5,4]);
  // => NaN

  Math.max.apply(null, [1,2,3,5,4]);
  // => 5
  //
  // It worked!
  // Apply took our array and applied
  // the numbers individually to Math.max for us
  // as if we applied them manually:
  //
  //         [1,2,3,5,4]
  // Math.max(1,2,3,5,4)

Or just assign a context

When does bind come in handy then?

Bind allows us to keep a function as a function. It does not invoke anything. It just takes care of binding either context or parameters or both. It is useful sometimes.

We could use bind to make sure functions we borrowed keep the original context:


  // Keep the context of `person` we borrowed function from 
  var borrowedStrictFullNameWithContext =
      person.fullName.bind(person);


  borrowedStrictFullNameWithContext();
  // => 'Jason Bourne'

See? We just picked a single method from an object and made sure it remembers where it came from by binding a context to it!

Wrapping up

To wrap everything up, here’s how you use call, bind and apply:


  someFunction.call(context, arg1, arg2, ..., argN);
  // => invokes function and returns results

  someFunction.apply(context, [arg1, arg2, ..., argN]);
  // => invokes function and returns results
                               
  someFunction.bind(context, arg1, arg2, ..., argN);
  // => returns a function with context / parameters bound

Further learnings

What’s next? Bind operator can do much more than setting the context. It can produce functions with [parameters bound]https://devdocs.io/javascript/global_objects/function/bind) as well. I’ll leave more advanced exercises with bind as a homework for you.

Good luck experimenting!

* global object - when writing JavaScript in your browser, the context referred by this operator is window. On the other hand, if you’re writing code in Node.js environment, your global object refers to global. What’s more, in strict mode, this refers to undefined if no context is bound. Make sure your functions using this operator always have context bound in strict mode. Otherwise your code might throw type errors, e.g. “TypeError: Cannot read property 'firstName' of undefined".

JavaScript Series: Adventures with Arrays | Reduce

We've learned about filter and map operations on JavaScript arrays already. Our possibilities do not end there, though.

Drawing conclusions

Often, once we mapped and filtered data, we'd like to draw some conclusions. Sometimes it's about taking a sum of all elements, sometimes it's about the average. Other times we'd like to accumulate results, e.g. count the number of occurrences.

What we're aiming to do is converting a collection into a single value. A value that represents our computation.

Reducing collections

Reducing is an operation that does exactly that with collections. It takes an initial value, current element and returns results of your computation. The next iteration starts with previously computed value and the next element. The process goes on until you run out of elements in the collection.

2b187026-6f93-4f74-b90e-e2444e59b8b6

The image above illustrates the process. Given an array of numbers 1, 2 and 3 we iterate over every element.

  • The process starts with an empty sum accumulator so we assign 0 to it. We add it to the first element and the result is 1
  • The next iteration starts with the previous sum which equals 1. We add it to the second element and the result is 3
  • In the final iteration, again we start with a previous sum accumulator which equals 3. We add the last number to it and the final result is 6

And that’s it! We just learned about reducing arrays!

Reducing in JavaScript

In theory this sound simple. Shall we be scared of JavaScript implementation? Not at all!

Like in the previous notes, we’re going to bring a collection of people data first.


  var people = [
    { gender: 'male', born: 1979 },
    { gender: 'female', born: 1976 },
    { gender: 'female', born: 1970 },
  ];

First exercise - count number of people born before 1975:

 
  function countBornBefore1975(sum, person) {
    if (person.born < 1975) {
      return sum + 1;
    }

    return sum;
  }

  
  // Count number of people born before 1975, start with 0
  people.reduce(countBornBefore1975, 0);
  // => 1

The number looks correct. How about counting number of female data records?


  function countFemaleRecords(sum, person) {
    if (person.gender === 'female') {
      return sum + 1;
    }

    return sum;
  }


  // Count number of female records, start with 0
  people.reduce(countFemaleRecords, 0);
  // => 2

Again, the number is correct!

How about solving a little bit more complex problem like getting a number of male and female records? Making two separate reduce operations sound like an option but there must be an easier way to achieve it. Right, there is!


  function countByGender(accumulator, person) {
    if (person.gender === 'male') {
      return {
        male:   accumulator.male + 1,
        female: accumulator.female,
      };
    }

    return {
      male:   accumulator.male,
      female: accumulator.female + 1,
    };
  }


  
  // Accumulate number of male and female records.
  // Use object literal. Start with zeros. 
  people.reduce(countByGender, { male: 0, female: 0 });
  // => { male: 1, female: 2 }

Perfect! As you can see, we can narrow things down not only to numbers, we can construct object literals and arrays out of initial collection as well!

You just learned about reducing arrays in JavaScript. You may perform some amazing computations when using it with map and filter. Full code listing to be found below.


  var people = [
    { gender: 'male', born: 1979 },
    { gender: 'female', born: 1976 },
    { gender: 'female', born: 1970 },
  ];



  function countBornBefore1975(sum, person) {
    if (person.born < 1975) {
      return sum + 1;
    }

    return sum;
  }

  // Count number of people born before 1975, start with 0
  people.reduce(countBornBefore1975, 0);
  // => 1



  function countFemaleRecords(sum, person) {
    if (person.gender === 'female') {
      return sum + 1;
    }

    return sum;
  }

  // Count number of female records, start with 0
  people.reduce(countFemaleRecords, 0);
  // => 2



  function countByGender(accumulator, person) {
    if (person.gender === 'male') {
      return {
        male:   accumulator.male + 1,
        female: accumulator.female,
      };
    }

    return {
      male:   accumulator.male,
      female: accumulator.female + 1,
    };
  }
  
  // Accumulate number of male and female records.
  // Use object literal. Start with zeros. 
  people.reduce(countByGender, { male: 0, female: 0 });
  // => { male: 1, female: 2 }

Now please go and try writing some reduce operations on your own, have fun!

JavaScript Series: Adventures with Arrays | Filter

In the last note we learned about applying a function to an array of elements. There is much more we can do with collections of elements. Ready to explore more advanced topics?

Before we move to some more complex operations there is one thing more we need to learn about.

When map fails

Last time we applied a function returning full name to a collection of objects containing first and last name. Remember the definition?

  
  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }

So long as we want to keep the same amount of results it’s fine to map items. However, sometimes we need to get rid of some of the results. Or, in other words, we want to keep some of them and forget about the others. Imagine an application that would let us filter people by gender. We have two females and one male in our collection:

  
  var people = [
    { firstName: 'Jason', lastName: 'Bourne' },
    { firstName: 'Nicky', lastName: 'Parsons' },
    { firstName: 'Marie', lastName: 'Kreutz' },
  ];

Okay, right, computer doesn’t know this. We should let it know:


  var people = [
    { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
    { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
    { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  ];                                          // ^ gender added
 

Much better! But still, we’re stuck. Shall we modify people collection and remove one of the items? What if we wanted to get the whole list back again?

As you might remember from the Approaching Problems note, JavaScript documentation contains a lot of useful information about methods Arrays, Strings and other types support.

The next method we’re going to learn about is filter.

When filter comes in handy

Filtering is an operation that allows us to keep the same number or less items depending on instructions we provide it with.

16ITAqOl0

The image above illustrates the process.

Given a collection of numbers 1, 2 and 3, we’d like to produce a new collection that contains only these items which equal 2. In our example a single-element array is produced. Remember, the function you provide to filter method should return either true or false.

Given our collection of people records:


  var people = [
    { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
    { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
    { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  ];
 

We’d like to write some functions that help us filter items based on the person’s gender. Here comes the first one:


  /**
   * Sample Usage:
   *
   *   alwaysTrue({ gender: 'male' });
   *   // => true
   *
   *   alwaysTrue({ gender: 'female' });
   *   // => true
   *
   */
  function alwaysTrue() {
    return true;
  }
  

Wait, what? What purpose does it serve? Correct, it returns a collection containing the same amount of items, let’s verify that:


  people.filter(alwaysTrue);
  // => 
  // [
  //   { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
  //   { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
  //   { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  // ]

The function above returns an array with an identical number of items input one had. How about writing some more functions?


  /**
   * Sample Usage:
   *
   *   isMale({ gender: 'male' });
   *   // => true
   *
   *   isMale({ gender: 'female' });
   *   // => false
   *
   */
  function isMale(person) {
    return person.gender === 'male';
  }

  /**
   * Sample Usage:
   *
   *   isFemale({ gender: 'male' });
   *   // => false
   *
   *   isFemale({ gender: 'female' });
   *   // => true
   *
   */
  function isFemale(person) {
    return person.gender === 'female';
  }
  

Time to test them out:


  people.filter(isMale);
  // => 
  // [
  //   { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
  // ]

  people.filter(isFemale);
  // => 
  // [
  //   { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
  //   { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  // ]
  

Pretty neat, huh? How about an edge case where none of the items would match?


  /**
   * Sample Usage:
   *
   *   alwaysFalse({ gender: 'male' });
   *   // => false
   *
   *   alwaysFalse({ gender: 'female' });
   *   // => false
   *
   */
  function alwaysFalse() {
    return false;
  }

And the results:


  people.filter(alwaysFalse);
  // => [ ]
  

Phew! At least we've got an array back!

Avoiding unnecessary computations

Since the map and filter operations always return an array as the result, we could chain the operations.

Once again, here’s our fullName function definition for reference:


  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }

Example: Take a list of people, compute their full names and show only females.

  
  // BAD! Will return an empty array, do you know why?
  people.map(fullName).filter(isFemale);
  // => [ ]

Can you spot the glitch above?

The reason an empty array is returned is simply because when we transform an array of person records into their full names, we lose an information about their gender thus none of them matches.

This leads to another thought - why did we compute full name for male records? This looks like an unnecessary step. Well spotted!

The resolution is quite simple - filter first, then map!


  people.filter(isFemale).map(fullName);
  // => [ 'Nicky Parsons', 'Marie Kreutz' ]
  

And the full code follows:

  
  var people = [
    { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
    { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
    { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  ];

  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }

  /**
   * Sample Usage:
   *
   *   alwaysTrue({ gender: 'male' });
   *   // => true
   *
   *   alwaysTrue({ gender: 'female' });
   *   // => true
   *
   */
  function alwaysTrue() {
    return true;
  }

  /**
   * Sample Usage:
   *
   *   alwaysFalse({ gender: 'male' });
   *   // => false
   *
   *   alwaysFalse({ gender: 'female' });
   *   // => false
   *
   */
  function alwaysFalse() {
    return false;
  }

  /**
   * Sample Usage:
   *
   *   isMale({ gender: 'male' });
   *   // => true
   *
   *   isMale({ gender: 'female' });
   *   // => false
   *
   */
  function isMale(person) {
    return person.gender === 'male';
  }

  /**
   * Sample Usage:
   *
   *   isFemale({ gender: 'male' });
   *   // => false
   *
   *   isFemale({ gender: 'female' });
   *   // => true
   *
   */
  function isFemale(person) {
    return person.gender === 'female';
  }

  people.filter(alwaysTrue);
  // =>
  // [
  //   { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
  //   { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
  //   { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  // ]

  people.filter(alwaysFalse);
  // => [ ]

  people.filter(isMale);
  // =>
  // [
  //   { firstName: 'Jason', lastName: 'Bourne', gender: 'male' },
  // ]

  people.filter(isFemale);
  // =>
  // [
  //   { firstName: 'Nicky', lastName: 'Parsons', gender: 'female' },
  //   { firstName: 'Marie', lastName: 'Kreutz', gender: 'female' },
  // ]

  // We lose information about gender after mapping to full name
  people.map(fullName).filter(isFemale);
  // => [ ]

  people.filter(isFemale).map(fullName);
  // => [ 'Nicky Parsons', 'Marie Kreutz' ]
 

Another lesson learned! Good luck experimenting!

JavaScript Series: Adventures with Arrays | Map

In the previous article we learned how to tackle problems one step at a time. It’s time to get some practice and learn a little bit about one of the most common structures found in JavaScript - Arrays.

If you don’t like the “Array” word then replace it with “Collection”. Collections hold items so they act as a grouping thus we can operate on all of them in the same manner.

When things don’t scale

Imagine the following code:

 
  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }
 

Given we have a function that takes person’s first and last name, we can apply it to an object:


  fullName({ firstName: 'Jason', lastName: 'Bourne' });
  // => Jason Bourne

We learned how to apply a function to a single person and return a string out of it. What if we had more records to operate on? Would it end up like this?


  var jasonBourne = fullName({
    firstName: 'Jason',
    lastName: 'Bourne',
  });
  // => 'Jason Bourne'

  var nickyParsons = fullName({
    firstName: 'Nicky',
    lastName: 'Parsons',
  });
  // => 'Nicky Parsons'

  var marieKreutz = fullName({
    firstName: 'Marie',
    lastName: 'Kreutz',
  });
  // => 'Marie Kreutz'

It does not scale, does it?

Map to the rescue

Mapping a collection means taking each of the elements, applying a function to that element and returning a totally new collection with the results.

158MLzhMj

The purpose of the drawing above is just to illustrate the process.

What you can see above is a collection of numbers 1, 2 and 3. We take a function that morphs each of those elements into the same number plus one and outputs a totally new collection with numbers 2, 3 and 4. Now let’s use the examples from the beginning of the article and learn how to use map in JavaScript.

Okay, first let’s bring back our fullName function definition:

  
  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }
 

Next, let’s define a variable holding a collection of people:


  var people = [
    { firstName: 'Jason', lastName: 'Bourne' },
    { firstName: 'Nicky', lastName: 'Parsons' },
    { firstName: 'Marie', lastName: 'Kreutz' },
  ];

Now that we have a collection of people and the mapping function, let’s map a collection to a new one:


  var fullNames = people.map(fullName);
  // => [ 'Jason Bourne', 'Nicky Parsons', 'Marie Kreutz' ]

That’s it! We just learned how to apply functions that operate on single items to a collection of items! Now please go and test your new skills in the JavaScript console.

Full code:


  /**
   * Sample Usage:
   *
   *   fullName({ firstName: 'Jason', lastName: 'Bourne' });
   *   // => 'Jason Bourne'
   *
   */
  function fullName(person) {
    return person.firstName + ' ' + person.lastName;
  }

  var people = [
    { firstName: 'Jason', lastName: 'Bourne' },
    { firstName: 'Nicky', lastName: 'Parsons' },
    { firstName: 'Marie', lastName: 'Kreutz' },
  ];


  var fullNames = people.map(fullName);
  // => [ 'Jason Bourne', 'Nicky Parsons', 'Marie Kreutz' ]
 

Very important - you don’t have to map numbers to numbers, strings to strings and so on. Actually, you can map items to any type you can imagine so long as you tell the JavaScript engine how to morph the value!

Good luck!