Last active
January 12, 2018 15:14
-
-
Save yuanchuan/c30f5c657f08115aee22ce3613fee4c4 to your computer and use it in GitHub Desktop.
Erlang-like pattern matching in JavaScript
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
const MARK = { | |
symbol: { | |
name: '__mark__', | |
type: 'condition', | |
}, | |
add(fn) { | |
let { name, type } = this.symbol; | |
let addon = { [name]: type }; | |
return Object.assign((...args) => fn(...args), addon); | |
}, | |
check(fn) { | |
if (!fn) return false; | |
let { name, type } = this.symbol; | |
return fn[name] == type; | |
} | |
}; | |
function toGroup(defs) { | |
return defs.reduce((group, def, i) => { | |
let next = defs[i + 1]; | |
MARK.check(def) || group.push({ | |
def: def, | |
cond: MARK.check(next) ? next : null | |
}); | |
return group; | |
}, []); | |
} | |
function getArgTypeList(def) { | |
let reg = /\((.+)\) (=>|\{)/; | |
return ((String(def).match(reg) || [])[1] || '') | |
.split(/, /) | |
.map(n => { | |
switch (n) { | |
case '[]': return { name: '[]' } | |
case '_': return { name: 'any' } | |
default: return { name: 'normal' } | |
} | |
}); | |
} | |
function compareArgs(args, typeList) { | |
for (let i = 0; i < typeList.length; ++i) { | |
let type = typeList[i], arg = args[i]; | |
if (type.name == '[]') { | |
return (JSON.stringify(arg) == type.name); | |
} | |
return true; | |
} | |
} | |
function when(cond) { | |
if (typeof cond !== 'function') { | |
cond = () => false; | |
} | |
return MARK.add((...args) => cond(...args)); | |
} | |
function define(...defs) { | |
let groups = toGroup(defs); | |
return (...args) => { | |
let argsLength = args.length; | |
for (let i = 0; i < groups.length; ++i) { | |
let { def, cond } = groups[i]; | |
if (argsLength != def.length) continue; | |
let isTypeMatched = compareArgs(args, getArgTypeList(def)); | |
if ((!cond && isTypeMatched) | |
|| (cond && cond(...args) && isTypeMatched)) { | |
return def(...args); | |
} | |
} | |
} | |
} | |
const fib = define( | |
N => 0, when(N => N == 0), | |
N => 1, when(N => N < 2), | |
N => fib(N - 1) + fib(N - 2) | |
); | |
// 6765 | |
console.log( fib(20) ); | |
const filter = define( | |
(L, F) => filter(L, F, []), | |
([], _, L1) => L1, | |
([H, ...T], F, L1) => filter(T, F, F(H) ? [...L1, H] : L1) | |
); | |
// [ 1, 3, 5, 7, 9 ] | |
console.log( | |
filter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], x => x % 2) | |
); | |
const foldl = define( | |
([], _, Acc) => Acc, | |
([H, ...T], F, Acc) => foldl(T, F, F(Acc, H)) | |
); | |
const plus = (a, b) => a + b; | |
const multiply = (a, b) => a * b; | |
// 10 | |
foldl([1, 2, 3, 4], plus, 0) | |
// 24 | |
foldl([1, 2, 3, 4], multiply, 1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment