-
-
Save zkat/c89deb618d0d8aeed04fb7b3c49a714a to your computer and use it in GitHub Desktop.
pattern matching sketch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// match ABNF | |
Match := 'match' '(' RHSExpr ')' '{' MatchClause* '}' | |
MatchClause := MatchClauseLHS '=>' FatArroyRHS MaybeASI | |
MatchClauseLHS := [MatcherExpr] (LiteralMatcher | ArrayMatcher | ObjectMatcher | JSVar) | |
MatcherExpr := LHSExpr | |
LiteralMatcher := LitRegExp | LitString | LitNumber | |
ArrayMatcher := '[' MatchClauseLHS [',', MatchClauseLHS]* ']' // and... whatever it takes to shove ...splat in there | |
ObjectMatcher := '{' (JSVar [':' MatchClauseLHS]) [',' (JSVar [':', MatchClauseLHS])]* '}' // see above note about ...splat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const util = require('util') | |
function mm (matcher, val) { | |
// We only invoke matchers if there's a `Symbol.match` mthod | |
const m = matcher[Symbol.match] | |
if (m) { | |
return m(val) | |
} else { | |
return val instanceof matcher | |
} | |
} | |
function mv (matcher, val) { | |
const v = matcher[Symbol.matchValue] | |
if (v) { | |
return v(val) | |
} else { | |
return val | |
} | |
} | |
function match (val, ...expressions) { | |
let matched | |
const expr = expressions.find(expr => { | |
matched = mv(expr.Matcher, val) | |
return mm(expr.Matcher, matched) | |
}) | |
return expr && expr.body(matched) | |
} | |
function expr (Matcher, body) { | |
return { | |
Matcher, | |
body | |
} | |
} | |
class Foo extends Object { | |
constructor (x, y) { | |
super() | |
this.x = x | |
this.y = y | |
} | |
} | |
function tryMatch (val) { | |
console.log('\nmatch', `(val = ${util.inspect(val)}) {`, '\n ', match(val, | |
// sugar: /foo(bar)/ [match, submatch] | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Array, val) | |
) | |
}, | |
[Symbol.matchValue] (val) { | |
return String(val).match(/foo(bar)/) | |
} | |
}, | |
([match, submatch]) => `/foo(bar)/ [match, submatch] => match === ${util.inspect(match)} && submatch === ${util.inspect(submatch)}` | |
), | |
// sugar: [a, b] | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Array, val) && | |
val.length === 2 | |
) | |
} | |
}, | |
([a, b]) => `[a, b] => ${util.inspect([a, b])}` | |
), | |
// sugar: [a, 2, ...rest] | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Array, val) && | |
val[1] === 2 | |
) | |
} | |
}, | |
([a, _, ...rest]) => `[a, 2, ...rest] => a === ${util.inspect(a)} && rest === ${util.inspect(rest)}` | |
), | |
// sugar: {y: {x: 'hello'}} | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Object, val) && | |
mm(Object, val.y) && | |
val.y.x === 'hello' | |
) | |
} | |
}, | |
({y: {x}}) => `{y: {x: 'hello'}} => x === ${util.inspect(x)}` | |
), | |
// sugar: {y: Foo {x: 'hello'}} | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Object, val) && | |
mm(Foo, val.y) | |
) | |
} | |
}, | |
({y: {x}}) => `{y: Foo {x}} => x === ${util.inspect(x)}` | |
), | |
// sugar: {x, x: {y}} | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Object, val) && | |
mm(Object, val.x) | |
) | |
} | |
}, | |
({x, x: {y}}) => `{x, x: {y}} => x === ${util.inspect(x)} && y === ${y} // (follows destr. syntax)` | |
), | |
// sugar: {y: {x}}: x + y | |
expr( | |
// Compound matcher generated | |
{ | |
[Symbol.match] (val) { | |
return ( | |
mm(Object, val) && | |
mm(Object, val.y) | |
) | |
} | |
}, | |
({y: {x}}) => `{y: {x}} => x === ${x} // (y is unbound)` | |
), | |
// sugar: Foo {x, y} | |
expr(Foo, ({x, y}) => `Foo {x, y} => x === ${x} && y === ${y}`), | |
// sugar: {x, y} | |
expr(Object, ({x, y}) => `{x, y} => x === ${x} && y === ${y}`), | |
// sugar: <literal number/string> | |
expr({ | |
[Symbol.match] (v) { return v === val } | |
}, (x) => `${util.inspect(val)} => val === ${util.inspect(x)}`) | |
), '\n}') | |
} | |
console.log('== basic match types ==') | |
tryMatch({x: 1, y: 2}) | |
tryMatch(new Foo(1, 2)) | |
tryMatch('hello') | |
tryMatch(1) | |
console.log('\n== array matching ==') | |
tryMatch([1, 2]) | |
tryMatch([1, 2, 3, 4, 5]) | |
console.log('\n== compound matching ==') | |
tryMatch({x: {y: 2}}) | |
tryMatch({y: {x: 1}}) | |
tryMatch({y: {x: 'hello'}}) | |
tryMatch({y: new Foo(1, 2)}) | |
console.log('\n== guards ==') | |
tryMatch([3,2,1]) | |
console.log('\n== using Symbol.matchValue api ==') | |
tryMatch(/foobar/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
== basic match types == | |
match (val = { x: 1, y: 2 }) { | |
{x, y} => x === 1 && y === 2 | |
} | |
match (val = Foo { x: 1, y: 2 }) { | |
Foo {x, y} => x === 1 && y === 2 | |
} | |
match (val = 'hello') { | |
'hello' => val === 'hello' | |
} | |
match (val = 1) { | |
1 => val === 1 | |
} | |
== array matching == | |
match (val = [ 1, 2 ]) { | |
[a, b] => [ 1, 2 ] | |
} | |
match (val = [ 1, 2, 3, 4, 5 ]) { | |
[a, 2, ...rest] => a === 1 && rest === [ 3, 4, 5 ] | |
} | |
== compound matching == | |
match (val = { x: { y: 2 } }) { | |
{x, x: {y}} => x === { y: 2 } && y === 2 // (follows destr. syntax) | |
} | |
match (val = { y: { x: 1 } }) { | |
{y: {x}} => x === 1 // (y is unbound) | |
} | |
match (val = { y: { x: 'hello' } }) { | |
{y: {x: 'hello'}} => x === 'hello' | |
} | |
match (val = { y: Foo { x: 1, y: 2 } }) { | |
{y: Foo {x}} => x === 1 | |
} | |
== guards == | |
match (val = [ 3, 2, 1 ]) { | |
[a, 2, ...rest] => a === 3 && rest === [ 1 ] | |
} | |
== using Symbol.matchValue api == | |
match (val = /foobar/) { | |
/foo(bar)/ [match, submatch] => match === 'foobar' && submatch === 'bar' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I believe all of the below are fully nestable | |
const foo = match (x) { | |
// Basic concepts. This is all you need to actually know. `Symbol.match` | |
// operates on this Object {}-style protocol in all cases. | |
// object-style destructuring. x, y are bound. | |
Just {val} => val | |
None {} => ... // (see below for alternative None) | |
// bind value to x, match with matcher. Toplevel reduntant; used for nesting | |
Matcher {} as x => x | |
// Syntax sugar extensions, all statically compile down to above. | |
// Desugars to `String/Number/RegExp {}` but compilers can special-case. | |
'literal' => ... | |
42 => ... | |
/regexp/ => ... | |
// Regexp has Array-like matcher. | |
// This desugars to RegExp {length: 2, 0: a, b: 2} | |
/regexp/ [a, b] => ... | |
// Desugars to Object {x, y} | |
{x, y} as obj => ... | |
// Desugars to Array {length: 2, 0: a, 1: b}. Array[Symbol.match] method enforces the "fail if wrong length" | |
[a, b] => ... | |
// Desugars to TypedArray {length: 2, 0: a, 1: b} | |
TypedArray [a, b] => ... | |
// `as` syntax allows omitting {} for matchers maybe? | |
Matcher as x => ... | |
// Plain variable matches without running a matcher. | |
// imo, this doesn't need a first-class fallthrough. I think encouraging | |
// people to have "fully qualified" matchers is important and | |
// regular variables can be used for fallthrough just fine | |
// Please no * nonsense plz | |
_ => 'just a variable' | |
other => console.log(other) // lol | |
// equality matches done with guards | |
other if other === 1 => 'other is 1' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment