Pattern Matching in Javascript with alexmerced-patternmatcher

Conditionals in Javscript

If Statements

if (color === "red"){
console.log("it's red")
}
if (color === "blue"){
console.log("it's blue")
}
if (color === "red"){
console.log("it's red")
}

This can be simplified with one line ifs…

if (color === "red") console.log("it's red")if (color === "blue") console.log("it's blue")if (color === "green") console.log("it's green")

or ternary operators

color === "red" ? console.log("it's red") : nullcolor === "blue" ? console.log("it's blue") : nullcolor === "green" ? console.log("it's green") : null

These are more succinct but if the actions that need to be taken if true get complex, then your back to the original more verbose if statements which can terse.

Switch Statements

switch(color){
case "red":
console.log("it's red")
break
case "blue":
console.log("it's blue")
break
case "green":
console.log("it's green")
break
default:
console.log("none of them")
}

This can be nice and more clear for situations like this. Another cool variation of this is to use a javascript object along with the dynamic keys to treat an object of functions as a switch.

const objectSwitch = {
red: () => console.log("it's red"),
blue: () => console.log("it's blue"),
green: () => console.log("it's green")
}
objectSwitch[color]() // <--- invoke the function behind which ever string in stored in color

This works great and has been a great solution for me but of course you run the risk on a key not present being in color and attempting to invoke undefined which will throw an error, so be careful.

The above are fine but only work if there is a match of values, not useful for more complex lists of conditionals.

A Trick for Regex

switch(true){
case /red/.test(color):
console.log("it's red")
break
case /blue/.test(color):
console.log("it's blue")
break
case /color/.test(color):
console.log("it's green")
break
default:
console.log("none of them")
}

This works for regex but what if you also want to check variable types, whether a key exist in an object, or the number of elements in a array, then just a big slew of ifs are your only choice.

Pattern Matching

So I decided to make my own Patter Matching library you can use… now.

npm install alexmerced-patternmatcher

How it works

const {createMatcher, createSingleMatcher, matchArray, matchObject} = require("alexmerced-patternmatcher")

  • create a matcher with createMatcher (allows multiple matches) or createSingleMatcher (allow for a single match), which returns a function who test the registered patterns against the value passed to it. createMatcher and createSingleMatcher take two arguments…
  • An arrays of arrays, each subarray made of up two elements, an expression in the string where v is the value being matched against, the second element is a function to run if the expression matces receives the value as an argument.
  • The second argument is an object of external values you want to use in expressions like classes for typechecking or other variables. These can be accessing by the string express under the namespace “ex”.
const matcher = createMatcher([
["v === 'red'", (v) => console.log("it's red")],
["v === 'blue'", (v) => console.log("it's blue")],
["v === 'green'", (v) => console.log("it's green")],
])
matcher(color)

I can now use this function wherever I like, so great for generating a matcher that may be used in multiple places in your app. With the matchArray function we can easily match this against an array of colors. It takes the array as the first argument and the matcher functions to use on the elements.

matchArray(["red", "green", "blue"], matcher)

Another great aspect of this is all these functions return you any return values the callbacks return.

Advanced Usage of patternmatcher

  • Without using the externals argument
/// CUSTOM TYPES CAT AND DOG
class Cat {
constructor(name, age){
this.age,
this.name
}
}
class Dog {
constructor(name, age){
this.age,
this.age
}
}
// AN ARRAY OF ANIMALS OF DIFFERENT TYPES
const animals = [
new Cat("Scratchy", 5),
new Dog("Spunky", 3),
new Cat("Paws", 3),
new Dog("Old Yeller", 10)
]
// LOOPING OVER ARRAY AND DOING DIFFERENT OPERATIONS BASED ON TYPE
const matcher = createMatcher([
["v.constructor.name === 'Dog'", (v) => console.log("It's a dog")],
["v.constructor.name === 'Cat", (v) => console.log("it's a cat")],
])
matchArray(animals, matcher)
  • version using the externals argument
const matcher = createMatcher([
["v instanceof ex.Dog", (v) => console.log("It's a dog")],
["v instanceof ex.Cat", (v) => console.log("it's a cat")],
], {Cat, Dog})
matchArray(animals, matcher)

As you can see the object passed in as the second argument becomes available through the ex namespace, this is important cause otherwise expressions cannot refer to external values like custom classes or variables.

Using PatternMatcher on an Object

const alex = {
name: "Alex Merced",
age: 36,
email: "alex@alexmercedcoder.com"
}
const matcher = createMatcher([
["v[0] === 'name'", ([key, value]) => console.log("do stuff with the name")],
["v[0] === 'age'", ([key, value]) => console.log("do stuff with the age")],
["v[0] === 'email'", ([key, value]) => console.log("do stuff with the email")],
])
matchObject(alex, matcher)

So if you have an array of users you need to perform operations on it would be as easy…

users.forEach(u => matchObject(u, matcher)) // <- run matcher on all users

Conclusion

--

--

Alex Merced is a Developer Advocate for Dremio and host of the Web Dev 101 and Datanation Podcasts.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alex Merced Coder

Alex Merced is a Developer Advocate for Dremio and host of the Web Dev 101 and Datanation Podcasts.