October 18, 2020
0 strokes bestowed

Pattern Matching in JavaScript

...well, sort of...

edit

Today, I want to share with you a coding pattern I frequently use when writing conditional JavaScript. I've made several tweets about it throughout the years and it's high time that I finally write a blog post that I can use as a reference. In fact, I use this pattern so often that my manager at work started to call it the "Kyle pattern". It shouldn't be called that, but I'll take it as a compliment.

I call what I'm about to show you "a poor person's pattern matching" or "makeshift pattern matching". It's not quite the real thing, but it does a pretty good job of coming close to it and is plenty useful.

Before you go further, you should understand switch statements pretty well. If you're not familiar with how they work in JavaScript, I recommend reading the MDN docs on switch. With that, let's get to it.

What is "pattern matching"?

I first encountered "pattern matching" when I started to learn ReasonML a few years ago. In ReasonML, a strongly typed functional language, you have variants. A variant, is one type in a set of types. With ReasonML's switch, when we pass in a value, we can respond differently based on the variant of that value. On their home page, they share this example:

// Variants
type schoolPerson = Teacher | Director | Student(string);

let greeting = person =>
  // Pattern matching switch
  switch (person) {
  | Teacher => "Hey Professor!"
  | Director => "Hello Director."
  | Student("Richard") => "Still here Ricky?"
  | Student(anyOtherName) => "Hey, " ++ anyOtherName ++ "."
  }

This mechanism quickly becomes the most robust way of doing conditional logic as you use the language. As you work with it, you get comfortable thinking in variants instead of thinking in if/elses or ternaries. But ReasonML's switch and JavaScript's switch are a bit different, so how can we recreate some of this magic in JavaScript?

Flipping switch on its head

When using a switch statement in JavaScript, we most commonly use the expression (the part in parentheses, which makes this parenthetical kind of meta) as if it's the left half of a strictly equals check (===), and each case as the right half. An example of this I expect a lot of you to be familiar with would be a state reducer a la Redux and/or React.

const reducer = (state, action) => {
  switch (action.type) {
    case 'DATA_REQUESTED':
      return { ...state, status: 'loading' }

    case 'DATA_RECEIVED':
      return { ...state, data: action.payload.data, status: 'loaded' }

    case 'DATA_FAILED':
      return { ...state, error: action.payload.error, status: 'error' }

    default:
      return state
  }
}

As we make calls to reducer, our switch tries to match the action.type to the case with the same string. However, this limits how useful our switch can be because we can only operate on a single value in the expression. Is there a way around this limitation?

There sure is.

Rhetorical question incoming. What are we doing with the switch expression and the cases exactly? We're strictly comparing (===) two values and seeing if they come out true or false.

Instead, what if we use switch (true) and make each of our cases an expression? This opens up some possibilities.

With each case as an expression, we can do things we couldn't before, such as:

  • Do multiple comparisons on the same value
  • Use predicate functions to match cases
  • Use multiple values in those predicate functions
  • Use RegExp.prototype.test to match patterns
  • and whatever else you can dream up

The key to using switch (true) in this way is to think of each case expression as a pattern. Sure, we can't match on variants in the wonderful way that ReasonML can, but we can use the tools we have to get the matches we want. Let's look at some examples.

In this first example, I've created an areWeThereYet function. It generates responses to that annoying question based on the milesToGo.

function areWeThereYet(milesToGo) {
  switch (true) {
    case milesToGo <= 1:
      return "We're hereeeee!"

    case milesToGo <= 10:
      return 'Almost. Just a little further.'

    case milesToGo <= 100:
      return 'A little more than an hour.'

    case milesToGo <= 250:
      return 'Just a half day away.'

    default:
      return "We're not even close so stop asking!"
  }
}

Personally, I find this easier to read than the same code with if/else if/elses. You might disagree, that's ok. Stick with me.

The next thing that makes switch (true) similar to pattern matching is the ability to now use functions in each case to generate matches. Let's consider the ReasonML example above, and adapt it a bit to JavaScript.

// I need you to use your imagination here, and pretend
// that all of these predicate functions exist and work correctly :)
const greeting = person => {
  switch (true) {
    case isTeacher(person):
      return 'Hey Professor!'

    case isDirector(person):
      return 'Hello Director.'

    case isRicky(person):
      return 'Still here Ricky?'

    case isStudent(person):
      return `Hey, ${person.name}.`
  }
}

There are other benefits to using a switch this way, such as case fall-throughs. Rather than writing it as an OR expression (||), you could write two cases that use fall-throughs to respond the same way. I'll "sanitize" a little snippet of an idea from work to show this.

// Again, use your imagination and envision a larger switch
switch (true) {
  // ...other cases

  case isEcommerceItems(items):
  case isCMSItems(items):
    return prepareForCollectionList(items)
}

There are a few other patterns to try with this, but I feel like I've given you a pretty good idea of how to get started. I'm trusting you to try it out and practice the pattern.

If you want an idea to get started with, you might try to solve the "Gilded Rose" kata with switch (true) and regexes. I was once given this kata as an interview question and I found my pattern matching trick to be a useful way to solve the problem.

Additional Notes

There is a TC39 proposal to add pattern matching to JavaScript, but it's been stalled at Stage 1 for quite some time. You can find the proposal repo here.

There is an interesting package called safety-match created by my Webflow coworker @suchipi. Check it out if you're interested in pattern matching with TypeScript.

Sharing this article on Twitter is a great way to help me out and I really appreciate the support.
+0
Liked the post? Click the beard a few times.
Tags
JavaScript
Newer Post: Headlight Vision

Let's talk some more about JavaScript, React, and software engineering.

I write a newsletter to share my thoughts and the projects I'm working on. I would love for you to join the conversation. You can unsubscribe at any time.

Data Structures and Algorithms Logo
Data Structures and Algorithms

Check out my courses!

Liked the post? You might like my video courses, too. Click the button to view this course or go to Courses for more information.
View on egghead.io
Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a front end web developer and software engineer who specializes in JavaScript and React.