Hacker News new | ask | show | jobs
by cshepp 2985 days ago
Pattern matching would be a great addition to JS. I built a pattern matching library[1] with a very similar syntax back in 2015 (although I will be the first to admit that it's a naive implementation). It makes validating/traversing deeply-nested objects much less verbose. I'm excited to see where this proposal goes.

[1] https://github.com/cshepp/Kasai

3 comments

Shameless plug but here is my pattern machter [1] :D

Example:

    // simple factorial
    const factorial = n => match(n)
        .when(0, 1)
        .otherwise(n => n * factorial(n - 1));

    // walking a tree
    class Tree {
        constructor(left, right) {
                this.left = left;
                this.right = right;
        }
    }

    class Node {
        constructor(value) {
                this.value = value;
        }
    }

    const T = (l, r) => new Tree(l, r);
    const N = v => new Node(v);

    const walkT = t => match(t)
        .when(Node, v => console.log(v.value))
        .when(Tree, t => { walkT(t.left); walkT(t.right)})
        .otherwise(_ => 'error');

    const mapT = (f, t) => match(t)
        .when(Node, v => N(f(v.value)))
        .when(Tree, t => T(mapT(f, t.left), mapT(f, t.right)))
        .otherwise(_ => { throw new Error('error') });
Works also on deeply nested objects.

[1] https://github.com/MarkusPfundstein/pmatch-js

Method-based syntax for pattern matching is easier to read and grok (IMO), but it lacks the dynamic capabilities of data-oriented syntax (in Kasai, patterns can be built/manipulated at runtime). It's interesting to compare the two approaches.
If you mean by method-based pattern matching that you evaluate a function for each 'case'. My lib can do that do:

    const match = require('pmatch-js')
    const _ = require('lodash')

    const fizzbuzz = x => match(x)
      .when(a => a % 3 == 0 && a % 5 == 0, 'fizzbuzz')
      .when(a => a % 5 == 0, 'buzz')
      .when(a => a % 3 == 0, 'fizz')
      .otherwise(a => a)

    console.log(
      _.range(1, 101).map(fizzbuzz).join(' ')
    )
But maybe you mean something totally different :-)

EDIT: Ah I think I know what you mean. Your lib takes an array of tuples to define the patterns. Sorry for the confusion.

Wow, importing the whole lodash to use just one function? There’s es6 way of getting an array with a range of numbers:

    [...Array(100).keys()].map(v=>v+1)
A little bit longer but no dependencies.
Although this method is pretty, I see 4 loops in this small line. a better way would be to only import the range method from lodash like so :

    import _range from "lodash/range";
Pretty cool trick mate. Didn’t know
I rather like your syntax compared to the proposed version. It is less concise but must more consistent and parsable:

  return match(user, [
          [{first: $, middle: $, last: $}, (f, m, l) => f + ' ' + m + ' ' + l],
          [{first: $, last: $}           , (f, l) => f + ' ' + l],
          [_, 'unknown']
      ]);
I think that this is not always guaranteed to work, because {first: $, middle: $, last: $} is the same as {first: $, last: $, middle: $}

... so calling Object.keys() is not necessarily going to use a consistent ordering. I think that is why they have to use the more verbose syntax in the other library.

I think you could do something like: {first: $.0, middle: $.1, last: $.2}, (f, m, l) => ... pretty easily, though.

I believe in es6 Object literals are guaranteed to keep their ordering, and Object.keys() will iterate through them in the order defined.
The ordering of Object.keys() is equal to insertion order of the keys into the object. So if you have an Object generation function, it can insert the keys in the proper order (and take advantage of stuff like v8 hidden classes)

http://2ality.com/2015/10/property-traversal-order-es6.html

Nice library! This actually looks great. The syntax is a bit noisey, but tbh not any more than the actual proposal.