loading...
Cover image for Pattern-Match your JavaScript with Z

Pattern-Match your JavaScript with Z

kayis profile image K ・2 min read

Cover image by Dennis Skley on Flickr

Functional programming techniques seem to be rather popular these days. People are using monads in JavaScript, write compilers in OCaml and even hardcore object-oriented languages like Java now support closures.

Wouldn't it be nice if we could use some of the higher-level abstractions, like pattern matching, in omnipresent languages like JavaScript?

There is an TC39 proposal for new syntax that enables this feature, but it just stage-1 and didn't see updates for nine months.

Luckily for us, there is a library called Z that lets us pattern match without the need for additional syntax.

Why

Pattern matching is a switch on steroids; you can create powerful conditions such based on Object properties or Array content without manipulating the Object or Array itself. That amount of power leads you to write functional, immutable, and expressive code instead imperative, which reduces a lot of complexity and bugs.

What

Z is a JavaScript library that allows us to use pattern matching in JavaScript without the need for new syntax. We install an NPM package and are ready to go!

How

The Z package exports a matches function we can use like we would use if or switch.

One main difference is, matches returns the case that matched, so it behaves like an expression instead of a statement. If nothing matched, it will return undefined.

So, what can it do?

Like switch it can match by value.

const result = matches(x)(
  (c = 1) => "One!",
  (c = 2) => "Two!",
  (c) => "Something different..."
);
Enter fullscreen mode Exit fullscreen mode

But, unlike switch, it can also match more complex values.

const result = matches(x)(
  (c = {data: 123}) => "We got data!",
  (c = {error: 404}) => "We got an error!",
  (c = [1,2,3]) => "We got an array!"
);
Enter fullscreen mode Exit fullscreen mode

It's also possible to match by type.

const result = matches(o)(
  (c = Date) => "We got the date " + c.toString(),
  (c = Number) => "We got the number " + c,
  (c = Array) => "We got an array!"
);
Enter fullscreen mode Exit fullscreen mode

We can also use it to destructure arrays or expect arrays with a specific length.

const result = matches(["hello", ",", "world", "!"])(
  (first, tail) => first + tail.join(""),
  (first, tail = []) => "Array only has one element!"
);
Enter fullscreen mode Exit fullscreen mode

Conclusion

Z uses standard JavaScript syntax, but it plays some tricks here. Seemingly it compares our value with the default values of our callback functions. This decision leads to the unusual usage of the = operator instead of the == and === operators.

It also seems to be planned to make a strict matching mode, that throws an error instead of returning undefined if a check wasn't exhaustive.

While I don't know if this is the best way to implement pattern matching in JavaScript it's at least an interesting one and could help to prevent some errors while leading to more concise code.

Tell me what do you think in the comments :)


If you like my posts, you can also follow me on Twitter!

Discussion

pic
Editor guide
Collapse
joelnet profile image
JavaScript Joel

This is incredible. I have a lot of questions regarding how this is done. I use Ramda's cond quite a bit. But I really like this syntax.

I'm curious to see how this works when minified. I believe it is using some type of reflection.

Collapse
kayis profile image
K Author

It seems to use a package called js-function-reflector which transforms a function object to it's string representation and parses it again.

github.com/arrizalamin/js-function...

Collapse
halcaponey_35 profile image
halcaponey

and js-function-reflector uses the toString() method availaible on functions

Thread Thread
joelnet profile image
JavaScript Joel

the toString()

That doesn't seem like it would work for minified code as the parameter names would be uglified.

Thread Thread
kayis profile image
K Author

It works as long as you don't compile the default arguments to ES5.

Collapse
joelnet profile image
JavaScript Joel

One limitation I see is something like this is not possible:

import allPass from 'mojiscript/logic/allPass'
import cond from 'mojiscript/logic/cond'

const isFizz = num => num % 3 === 0
const isBuzz = num => num % 5 === 0
const isFizzBuzz = allPass ([ isFizz, isBuzz ])

const fizziness = cond ([
  [ isFizzBuzz, 'FizzBuzz' ],
  [ isFizz, 'Fizz' ],
  [ isBuzz, 'Buzz' ],
  [ () => true, x => x ]
])

fizziness(1) //=> 1
fizziness(3) //=> 'Fizz'
fizziness(5) //=> 'Buzz'
fizziness(15) //=> 'FizzBuzz'
Collapse
khalyomede profile image
Khalyomede

Very interesting, I would use it mostly to do validation. Looking forward to see what it becomes in the future.

Collapse
kayis profile image
K Author

Yes, I think the type-checking is the most practical feature of it, if it's exhaustive it could prevent many typical JavaScript bugs :)

Collapse
toastking profile image
Matt Del Signore

Yeah, in functional languages it usually replaces conditionals but javascript has a plethora of them. This is super cool though.

Collapse
khalyomede profile image
Khalyomede

Absolutely agree, I use Joi but it still seems not "natural enough" for me, always thought Javascript diserved something better. Here you go :)

Collapse
maxdevjs profile image
maxdevjs

Life's filling up with more and more z and z and... Good catch, did not know about this lib :)

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
kayis profile image
K Author

Glad, you like it :)