adobe / ferrum

Features from the rust language in javascript: Provides Traits/Type classes & a hashing infrastructure and an advanced library for working with sequences/iterators in js
https://www.ferrumjs.org
Apache License 2.0
515 stars 24 forks source link

Compex pattern matching #135

Open koraa opened 3 years ago

koraa commented 3 years ago

Is it possible to find a construction for advanced pattern matching including type signatures in vanilla* js?

The following construction doesn't seem very sound to me, but maybe we can come up with something.

// Turns numbers into strings, strings into numbers, and throws errors for anything else
patternMatch(
  is_a(String),    Number,
  is_a(Number), String,
  yes,                 identity);
tryPatternMatch(
  is_a(String),    Number,
  is_a(Number), String);

exec(Y(rematch => patternMatch(
  is_any([String, Number]), (v) => rematch([v]),
  is_a(Array), map(x => Number(x)*2 + 1),
)));

*vanilla – as in no transpilers or compilers involved

koraa commented 3 years ago

Update: We might be able to model this as a trait:

const PatternMatching = Trait("PatternMatching");
// Implementation for primitives: by identity
// Implementation for arrays, objects, etc: By recursion
// Implementation for regex: As group match

const tryMatch = (targ, default, pattern) =>
  ifdef(patternMatching.invoke(pattern, targ), (m) => Object.assign([], m));

const match = (targ, pattern) => {
  const None = Symbol();
  const m = tryMatch(targ, None, pattern);
  if (m === None) throw MatchFailed(/* … */);
  rerturn m;
};

const AnyMatcher = Tuple('any', [], {
  [PatternMatching.sym](value, matches) {
    matches.push(value);
  }
});

const any = AnyMatcher.new();
const any_as = NamedAnyMatcher.new;

const group = (name, pattern) => … // produces an entire subgroup match
const as = (name, pattern) => … // just binds the name
const when = (predicate) => … // Just executes the predicate

const anyof = (patterns) => … // allows multiple patterns
const allof  = (patterns) => … // requires multiple patterns to match

const exact = (value) => … // match by identity
const equality = (value) => … // match anything that equals the given predicate

const subtype = (value) =>
const type = (value) =>
const classy = (type, records) =>

const if_else_match = (targ, default, pattern, fn) => {
  const None = Symbol();
  const m = tryMatch(targ, None, pattern);
  return m === None ? default : fn(m);
};

const if_match = (targ, pattern, fn) => if_else_match(targ, undefined, pattern, fn);

const match_on = (value, patterns…) => …

const fibonacci = (x) => match_on(x,
  match(0, _ => 0),
  match(1, _ => 1),
  match(any, (_) => fibonacci(x-1) + fibonacci(x-2)
);

// Complex example

const Add = Tuple('Add', ['a', 'b']);
const Mul = Tuple("Mul", ['a', 'b']);
const calc = (tree) => match_on(tree,
  match(Add(any, any), ([a, b]) => a+b),
  match(Mul(any, any), ([a, b]) => a*b),
  match(Number, (x) => x,
  /* throws */);
calc(2) // 2
calc(Mul(Add(2, 3), 4)) // 20

This basically models matching as a trait where the implementation is given the object to be matched; if the object matches successfully, the implementation returns an array that may contain number and string keys matched otherwise undefined is returned. Some standard types such as Object, Array, etc and Primitive types are implemented by default. Containers recurse and primitive types compare by identity. RegExps are integrated into matching. This probably needs to be extended to support customizers.