CrypticSwarm / js-matcher

Provides simple pattern matching over JavaScript objects.
11 stars 1 forks source link

Add support for regexp-like matching #1

Open msackman opened 12 years ago

msackman commented 12 years ago

I like the look of this library a lot and it's very close to meeting my needs. But I'd quite like to see some regexp support:

Currently, the pattern can contain either fully ground values, or variable-placeholders. It would be nice to allow those variable-placeholders to contain regexps sort of maybe in the style of https://github.com/squaremo/rejson

So maybe something like:

match({ a: '$a =~ /\\d+/' }, { a: 5 });

would only match if a is assigned a number. There are some more interesting open questions though, such as:

What do you think? I'm happy to try and hack some of this stuff in myself, but I wonder if you've already had thoughts along these lines?

CrypticSwarm commented 12 years ago

I've thought about some of these things. I've considered some things like that that need parsing, and also considered things like this that need less parsing:

match({ "!a": { value: '\\d', type: 'regex', optional: true } })

It might be worth looking at ClojureScript. I believe they might have gotten both clojure core.match and core.logic running in JS. Also interesting on the ecma script discuss list there has been a discussion on pattern matching just in the last hour (https://mail.mozilla.org/pipermail/es-discuss/2012-April/022395.html)

msackman commented 12 years ago

Interesting stuff. I like the fact the patterns here are structurally isomorphic to the shape of the value being matched against. It's a shame to lose that but if you don't, you end up encoding richer patterns in the text of the match which then starts to introduce a whole sub query language... it gets messy fast. Difficult to know what's best to do.

One other thing I quite fancied trying to add was better nesting so you could do something like:

match({ a: match({foo: 'bar', baz: '$qux'}), b: '$b'}, {a: {foo: bar, baz: 5}, b: 'c'})

but then you really need match to become some sort of (E)DSL constructor rather than a verb. Or at least provide a suitable DSL constructor.

CrypticSwarm commented 12 years ago

Your example, what does it buy you? What would that do? match takes two args. The inside match in the example only has one.

I agree having the shape of the code be the same is a good thing. One of the reasons I haven't done anything like this so far.

From what I can guess you are trying to do with the nested match is to bind the whole section to a variable and also bind some of the parts to variables also. So if we had some features like an 'as' keyword we could do the following:

match({ a: { foo: 'bar', baz: '$quz' } as '$a', b: '$b' }, 
      {a: {foo: bar, baz: 5}, b: 'c'})  
// { a: { foo: 'bar', baz: '$quz' }, quz: 5, b: 'c' }

Sadly that isn't possible in that form. However if we had a normal function called composeAndMerge which receives the obj to match against first and an arbitrary number of patterns to match it would match the first pattern then take the bindings object and pass that to the next pattern. If the next one matched it combines merges the two bindings objects together and passes to the next or returns.

composeAndMerge({a: {foo: bar, baz: 5}, b: 'c'}, { a: '$hello' , b: '$b'},
                { hello: {foo: 'bar', baz: '$qux'} }) 
// { hello:  {foo: bar, baz: 5}, qux: 5, b: 'c' }
msackman commented 12 years ago

Yeah, sorry, I overloaded match badly there. I was meaning it more as an ADT constructor in the inner case, saying "the value of a should match against this thing". I read through the ecma script thread you pointed to - interesting stuff, many thanks. I think use of as is intuitive provided the language gains full pattern matching and then as is then just an aliasing technique, a la Haskell or Erlang.

The composeAndMerge idea is interesting. I can certainly see that working, though the scoping rules would be interesting - you could almost see an RPN style working well!