Last active
March 19, 2022 06:08
-
-
Save nexpr/12c8e9661280e620ef6853a9aa8a4eea to your computer and use it in GitHub Desktop.
JavaScript pattern matching
This file contains hidden or 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
const A = gen() | |
const B = gen() | |
const a = A({ v: 0 }) | |
const b = B({ v: 1 }) | |
class C {} | |
const c = new C() | |
const values = [ | |
new Date(), | |
new Date("INVALID"), | |
/a/g, | |
9, | |
3, | |
"abc", | |
true, | |
{ x: 1 }, | |
{ y: 2 }, | |
{ foo: "a", bar: true }, | |
{ foo: 1, bar: "x" }, | |
{ p: { q: new Date() } }, | |
{ p: { q: new RegExp() } }, | |
a, | |
b, | |
c, | |
] | |
for (const value of values) { | |
const result = match(value, [ | |
[Date, v => isNaN(v), () => -1], | |
[Date, v => v.getFullYear()], | |
[RegExp, v => v.flags], | |
[null, v => v?.x, v => `x is ${v.x}`], | |
["number", v => v > 5, () => ">5"], | |
["number", v => v > 3, () => ">3"], | |
["number", () => "num"], | |
["boolean", () => 100], | |
[{ foo: "string", bar: "boolean" }, ({ foo, bar }) => `${foo} / ${bar}`], | |
[{ p: { q: Date } }, ({ p: { q }}) => `p:q: ${+q}`], | |
[A, () => "A"], | |
[B, () => "B"], | |
[C, () => "C"], | |
[null, () => "N"], | |
]) | |
console.log("%o => %o", value, result) | |
} | |
/* | |
Sat Mar 19 2022 14:29:40 GMT+0900 (日本標準時) => 2022 | |
Invalid Date => -1 | |
/a/g => 'g' | |
9 => '>5' | |
3 => 'num' | |
'abc' => 'N' | |
true => 100 | |
{x: 1} => 'x is 1' | |
{y: 2} => 'N' | |
{foo: 'a', bar: true} => 'a / true' | |
{foo: 1, bar: 'x'} => 'N' | |
{p: {…}} => 'p:q: 1647667780906' | |
{p: {…}} => 'N' | |
{v: 0, Symbol(): ƒ} => 'A' | |
{v: 1, Symbol(): ƒ} => 'B' | |
C {} => 'C' | |
*/ | |
for (const value of [1, 2, 3]) { | |
const result = matchEq(value, [ | |
[1, () => "A"], | |
[2, () => "B"], | |
[3, () => "C"], | |
]) | |
console.log("%o => %o", value, result) | |
} | |
/* | |
1 => 'A' | |
2 => 'B' | |
3 => 'C' | |
*/ |
This file contains hidden or 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
const type_key = Symbol() | |
export const gen = () => { | |
const f = (value) => { | |
return { [type_key]: f, ...value } | |
} | |
return f | |
} | |
export const match = (value, patterns) => { | |
const matchType = (type, value) => { | |
switch (typeof type) { | |
case "string": | |
return typeof value === type | |
case "function": | |
if (value?.[type_key]) { | |
return value?.[type_key] === type | |
} else { | |
return value?.constructor === type | |
} | |
case "object": | |
if (type === null) { | |
return true | |
} else if (Array.isArray(type)) { | |
return type.some(t => matchType(t, value)) | |
} else { | |
return Object.entries(type).every(([k, v]) => { | |
return matchType(v, value?.[k]) | |
}) | |
} | |
} | |
throw new Error("invalid type") | |
} | |
const pattern = patterns.find(([type, where, fn]) => | |
matchType(type, value) && (fn ? where(value) : true) | |
) | |
if (pattern) { | |
return pattern[2] ? pattern[2](value) : pattern[1](value) | |
} else { | |
throw new Error("no matched pattern") | |
} | |
} | |
export const matchEq = (value, patterns) => match(value, patterns.map(([v, fn]) => [null, x => x === v, fn])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment