Skip to content

Instantly share code, notes, and snippets.

@nexpr
Last active March 19, 2022 06:08
Show Gist options
  • Save nexpr/12c8e9661280e620ef6853a9aa8a4eea to your computer and use it in GitHub Desktop.
Save nexpr/12c8e9661280e620ef6853a9aa8a4eea to your computer and use it in GitHub Desktop.
JavaScript pattern matching
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'
*/
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