Contenu connexe Similaire à Implementing pattern-matching in JavaScript (full version) (20) Plus de François-Guillaume Ribreau (16) Implementing pattern-matching in JavaScript (full version)4. @FGRibreau
“In computer science, pattern matching is
the act of checking a given sequence of
tokens for the presence of the constituents
of some pattern.” — wikipedia
5. @FGRibreau
def toYesOrNo(choice: Int): String = choice match {
case 1 => "yes"
case 0 => "no"
case _ => "error"
}
def fact(n: Int): Int = n match {
case 0 => 1
case n => n * fact(n - 1)
}
Pattern Matching in Scala
15. @FGRibreau
links.map(link => {
[{protocol: 'HTTP'}]: () => 1,
[{protocol: ‘AMQP'}]: () => 2
});
links.map(link => {
[{protocol: 'HTTP'}]: 1,
[{protocol: ‘AMQP'}]: 2
});
links.map(link => {
[{protocol: 'HTTP'}]: => 1,
[{protocol: 'AMQP'}]: => 2
});
17. @FGRibreau
links.map(link => {})
[undefined, undefined]
links.map(link => {1})
[undefined, undefined]
links.map(link => {return 1})
[1,1]
links.map(link => 1)
[1,1]
Syntactically valid, semantically invalid
… but then I won't have my pattern matching.
19. @FGRibreau
If I go from there…
_.flatten(links).map(link => {
[{protocol: 'HTTP'}]: => 1,
[{protocol: 'AMQP'}]: => 2
});
20. @FGRibreau
If I go from there…
_.flatten(links).map(link => {
[{protocol: 'HTTP'}]: => 1,
[{protocol: 'AMQP'}]: => 2
});
_.flatten(links).map(match({
[{protocol: 'HTTP'}]: 1,
[{protocol: 'AMQP'}]: 2
}));
…to there…
21. @FGRibreau
… then it’s syntactically correct!
_.flatten(links).map(match({
[{protocol: 'HTTP'}]: 1,
[{protocol: 'AMQP'}]: 2
}));
22. @FGRibreau
… then it’s syntactically correct!
_.flatten(links).map(match({
[{protocol: 'HTTP'}]: 1,
[{protocol: 'AMQP'}]: 2
}));
23. @FGRibreau
… would be great too !
const linkNumber = match(link,{
[{protocol: 'HTTP'}]: 1,
[{protocol: 'AMQP'}]: 2
});
25. @FGRibreau
{
[{protocol: ‘HTTP'}]: 1,
[{protocol: ‘AMQP’}]: 2
}
ES6 "computed property names"
“The object initializer syntax also supports computed property names.
That allows you to put an expression in brackets [], that will be computed as
the property name.”
29. @FGRibreau
evaluates to
ES6 "computed property names"
{
[when({protocol: ‘HTTP’})]: 1,
[when({protocol: ‘AMQP'})]: 2,
[when()]: 0,
}
{
'{"protocol":"HTTP"}': 1,
'{"protocol":"AMQP"}': 2,
Symbol('match.pattern.catchAll'): 0
}
30. @FGRibreau
evaluates to
ES6 "computed property names"
{
[when({protocol: ‘HTTP’})]: 1,
[when({protocol: ‘AMQP'})]: 2,
[when()]: 0,
}
{
'{"protocol":"HTTP"}': 1,
'{"protocol":"AMQP"}': 2,
Symbol('match.pattern.catchAll'): 0
}
31. @FGRibreau
ES6 "computed property names"
{
[when({protocol: ‘HTTP’})]: 1,
[when({protocol: ‘AMQP'})]: 2,
[when()]: 0,
}
{
'{"protocol":"HTTP"}': 1,
'{"protocol":"AMQP"}': 2,
Symbol('match.pattern.catchAll'): 0
}
function when(props){
if(props === undefined){
return _catchAllSymbol;
}
return _JSON.stringify(props);
}
32. @FGRibreau
Order is lost by match’s object
.map(match({
[when.range(0, 43)]: 42,
[when(42)]: 72
}))
when.range(0,43) => '["Symbol(match.pattern.RANGE)",0,43]'
when(42) => '[42]'
JS objects are an unordered collection of properties
40. @FGRibreau
How to get matched value?
const fact = match({
[when(0)]: 1,
[when()]: n * fact(n-1)
});
fact(10);
n is not defined
41. @FGRibreau
How to get matched value?
const fact = match({
[when(0)]: 1,
[when()]: n * fact(n-1)
});
fact(10);
simple, use a function:
const fact = match({
[when(0)]: 1,
[when()]: (n) => n * fact(n-1)
});
fact(10); // 3628800
42. @FGRibreau
How to get matched value?
const fact = match({
[when(0)]: 1,
[when()]: n * fact(n-1)
});
fact(10);
simple, use a function:
const fact = match({
[when(0)]: 1,
[when()]: (n) => n * fact(n-1)
});
fact(10); // 3628800
43. @FGRibreau
const input = [{protocol: 'HTTP', i:10}, {protocol: 'AMQP', i:11},
{protocol: 'WAT', i:3}];
const output = input.map(match({
[when({protocol:'HTTP'})]: (i) => () => 1,
[when({protocol:'AMQP'})]: (i) => () => 2,
[when()]: (i) => () => 0,
}));
output.map((f) => f()) // => [1, 2, 20]
But how do I yield functions?
45. @FGRibreau
function match(/* args... */){
const args = Array.from(arguments),
obj = args[args.length-1];
// pre-compute matchers
let matchers = [];
for(let key in obj){
matchers.push(when.unserialize(key, obj[key])); // e.g. {match:(mixed)=>boolean,result:mixed,position:number}
}
matchers.sort(function(a, b){
return a.position < b.position ? -1 : 1;
});
if(Object.getOwnPropertySymbols(obj).indexOf(_catchAllSymbol) !== -1){
matchers.push(when.unserialize(_catchAllSymbol, obj[_catchAllSymbol]));
}
const calculateResult = function(input){
const matched = matchers.find((matcher) => matcher.match(input));
if (!matched) {
throw new MissingCatchAllPattern();
}
return typeof matched.result === 'function' ? matched.result(input) : matched.result;
};
return args.length === 2 ? calculateResult(args[0]) : calculateResult;
}
46. @FGRibreau
when.unserialize = function(serializedKey, value){
if(serializedKey === _catchAllSymbol){
return {
match: _true, // same as: () => true
result: value,
position: Infinity
};
}
const {position, matcherConfiguration} = _unserialize(serializedKey);
return {
match: _match(matcherConfiguration),
result: value,
position: position
};
};
47. @FGRibreau
function _match(props){ // [{type}, …]
if(Array.isArray(props)){
if(props[0] === _patternORStr){ // _patternORStr = Symbol(‘OR’)
props.shift();
return function(input){
return props[0].some((prop) => _matching(prop, input));
};
}
if(props[0] === _patternANDStr){ // _patternANDStr = Symbol(‘AND’)
props.shift();
return function(input){
return props[0].every((prop) => _matching(prop, input));
};
}
if(props[0] === _patternRANGEStr){ // _patternRANGEStr = Symbol(‘RANGE’)
props.shift();
return function(input){
return props[0] <= input && input <= props[1];
};
}
}
function _matching(props, input){
// [...]
if(props instanceof RegExp){
return props.test(input);
}
if(typeof input === 'object'){
for(let prop in props){
if(input.hasOwnProperty(prop) && input[prop] !== props[prop]){
return false;
}
}
return true;
}
return props === input;
}
return (input) => _matching(props, input);
}
52. @FGRibreau
function replaceInstanceId(elem) {
if (_.isPlainObject(elem)) {
return _.mapValues(elem, replaceInstanceId);
} else if (_.isArray(elem)) {
return _.map(elem, replaceInstanceId);
} else if (_.isString(elem) && _.includes(elem, 'instance_id')) {
return _.template(elem)({instance_id: conf.instance_id});
}
return elem;
}
Type Matching
const replaceInstanceId = match({
[when(Object)]:(elem) => _.mapValues(elem, replaceInstanceId),
[when(Array)]:(elem) => _.map(elem, replaceInstanceId),
[when.and(String, /instance_id/)]: (elem) => _.template(elem)({instance_id:
conf.instance_id}),
[when()]: _.identity
});
With type matching
53. @FGRibreau
const replaceInstanceId = match({
// extraction
[when({a:when._, b:when._, c:{e:when._})]:(a, b, e) => {a, b, e},
[when(Array)]:(elem) => _.map(elem, replaceInstanceId),
[when()]: _.identity
});
Pattern Extraction