3 min read
Pattern Matching in JavaScript
Pattern-matching is something I've leaned-on a lot recently, and I'm starting to anticipate its inclusion in the language more and more. It's currently a Stage 1 proposal.
Maybe to help it along I can share this little library I've been tinkering with.
I've tried to, ahem, match the TC39 spec as closely as I could in anticipation of the hopeful day when I don't need to use it anymore:
import { match, otherwise, when } from 'match-iz'
let result = match(data)(
when(pattern, result || handler),
when(pattern, result || handler),
otherwise(result || handler)
)
# Why not if / switch?
If pattern-matching is new to you, it's essentially a declarative version of
if and switch, where you describe the expected shape of your data using
"patterns".
Patterns are a combination of both functions and data, and because of this certain assumptions can be made to help reduce the amount of boilerplate normally required to check that your data looks a certain way:
// Imperative:
if (typeof res?.statusCode === 'number') {
if (res.statusCode >= 200 && res.statusCode < 300) {
return res.body
}
}
// Declarative:
return match(res)(
when({ statusCode: inRange(200, 299) }, () => res.body),
otherwise(() => {})
)
match-izwill check thatstatusCodeis a key ofresby implication of thewhen()being passed an object-literal{ ... }.- The
inRange()pattern-helper guards against non-numbers before trying to determine if its input is within a certain range.
You can view more imperative vs. declarative examples over on the match-iz README.md.
# How does it work?
Let's expand a little on the above example.
const res = {
statusCode: 200,
body: 'Hello, world!'
}
We want to check the object res has a statusCode prop with a value between
200 and 299 inclusive, and we want to use another object to help us do
that, like this one:
const matcher = {
statusCode: value => {
return typeof value === 'number' && value >= 200 && value <= 299
}
}
To use the matcher to check res in a naive and hard-coded way, we can do
something like this:
const key = 'statusCode'
const valueToCheck = res[key]
const valueChecker = matcher[key]
const foundMatch = Object.hasOwn(res, key) && valueChecker(valueToCheck)
match-iz basically does this for us: It will look at all the keys of
matcher to see if they are contained in the res object. For any that exist,
it will then check to see if the value within the corresponding matcher is
either literal, or a function, and use that to determine if the res value is
a match:
return match(res)(
when({ statusCode: inRange(200, 299) }, () => res.body),
otherwise(() => {})
)
inRange (a pattern
helper)
is essentially returning a function that works the same way as the one we see
in matcher.statusCode. It looks a bit like this:
function inRange(fromInclusive, toInclusive) {
return valueToCheck => {
return (
typeof valueToCheck === 'number' &&
valueToCheck >= fromInclusive &&
valueToCheck <= toInclusive
)
}
}
The match/when combination are compiling and running such matchers.
# In the Wild
I've used match-iz myself for these other little projects:
sift-r and
viddy.
If you'd like to try a different tool, there are many other similar libraries) for adding pattern-matching to your JavaScript.
- A version of this article has been published on Dev.to.
18 Aug, 2021