I propose match not only pattern but also type (by comparing constructors or class ids) at the same type as doing in many langs. And use .. (double dot) for skipping this:
const getLength = vector => match (vector) {
.. ({ x, y, z }) Math.hypot(x, y, z);
.. ({ x, y }) Math.hypot(x, y);
.. ([...etc]) vector.length;
};
is same as current proposal but if you match only Vec3 class it may look like this:
class Vec3 { constructor(x, y, z) { ... } }
const getLength = vector => match (vector) {
Vec3 ({ x, y, z }) Math.hypot(x, y, z);
Vec3 ({ x, y }) Math.hypot(x, y);
Vec3 () vector.len();
.. ([...etc]) vector.length; // ArrayLike
.. { /* deafult */ 0; } // others
};
similar example on Python3:
def getLength(vector):
match vector:
case Vec3(x, y, z):
return math.hypot(x, y, z)
case Vec3(x, y):
return math.hypot(x, y)
case Vec3:
return vector.len()
case (x, y, z): # vector like
return math.hypot(x, y, z)
case _:
print('invalid')
"type" isn't a thing in JS beyond typeof. .constructor is not reliable. There's no such thing as a "class ID".
To do what you're suggesting would require using instanceof semantics, which is:
a) a false negative for cross-realm builtins
b) forgeable - anything can use Symbol.hasInstance to lie and wrongly claim something is an instance, and builtins can be mutated to lie and wrongly claim something is not.
The way to do what you want is this:
const getLength = vector => match (vector) {
when ({ x, y, z }) if (vector instanceof Vec3) { Math.hypot(x, y, z); }
when ({ x, y }) if (vector instanceof Vec3) { Math.hypot(x, y); }
if (vector instanceof Vec3) { vector.len(); }
.. ([...etc]) vector.length; // ArrayLike
.. { /* default */ 0; } // others
};
The way to do what you want is this:
Yes, but this much more verbose. Also .. or leading type will replace "when". All this should improve ergonomics and make everything less verbose
It is much more verbose, but it should be, since it鈥檚 semantics won鈥檛 be what everyone wants. Explicitness is better than brevity when the concise version isn鈥檛 consistently intuitive.
matching by Type is very important feature for many scenarios. Just see this article and code example which use switch (true) with isString, isDefined and etc.
Also it very useful for syntax parsing:
https://github.com/jquery/esprima/blob/main/test/3rdparty/mootools-1.4.5.js#L346
https://github.com/jquery/esprima/blob/main/test/3rdparty/mootools-1.4.5.js#L360
https://github.com/jquery/esprima/blob/main/test/3rdparty/mootools-1.4.5.js#L522
https://github.com/microsoft/TypeScript/issues/2214
https://github.com/facebook/flow/issues/1595
https://github.com/facebook/flow/blob/afdc6d3c3a018518d3ca58b77189c4077e4fcd1a/packages/flow-dev-tools/src/test/builder.js#L560
https://dev.realworldocaml.org/parsing-with-ocamllex-and-menhir.html
A few of those examples use typeof semantics, which is much simpler use case than matching a constructor.
The linked Flow issue is closed because
constructor check doesn't really guarantee anything.
but if a constructor check is what's really wanted here, can't you do
match(vector) {
when ({ x, y, z, constructor: ^Vec3 }) { Math.hypot(x, y, z); }
}
assuming Vec3 doesn't have Symbol.matcher. If it does have Symbol.matcher, you could work around it with a wrapper
const is = (value) => ({
[Symbol.matcher](matchable) {
if (Object.is(value, matchable)) {
return {
matched: true,
value: value
};
}
})
match(vector) {
when ({ x, y, z, constructor: ^is(Vec3) }) { Math.hypot(x, y, z); }
}
Similarily, the mootools examples use their own typeOf function, which seems like something you could do with a custom matcher for the pin operator.
@MaxGraey in languages with that concept, i agree - JS does not have it.
The custom matcher protocol will let you define your own semantics so that you could do this:
const getLength = vector => match (vector) {
when ^Vec3 and ({ x, y, z }) { Math.hypot(x, y, z); }
when ^Vec3 and ({ x, y }) { Math.hypot(x, y); }
when ^Vec3 { vector.len(); }
.. ([...etc]) vector.length; // ArrayLike
.. { /* default */ 0; } // others
};
Similarily, the mootools examples use their own typeOf function, which seems like something you could do with a custom matcher for the pin operator.
The main idea provide stable and consistent matching by Type as well. It may be something special than just val.constructor == String if it's possible.
Basically all languages provide this feature. It's pretty important ingredient of PM which also will allow semantic checkers like typescript add exhaustive pattern analysis:
https://tkdodo.eu/blog/exhaustive-matching-in-type-script
https://fsharpforfunandprofit.com/posts/correctness-exhaustive-pattern-matching
The custom matcher protocol will let you define your own semantics so that you could do this:
Yes, but it's too complicated as for user space and js engines
I have not heard any feedback yet that it's too complicated for either.
Basically all languages provide this feature.
JavaScript does not provide this feature, and as such, pattern matching can not provide it either.
A separate proposal (which is already in progress) would provide a mechanism for this, and there'd surely be a way to integrate the two at that time.
A separate proposal (which is already in progress) would provide a mechanism for this, and there'd surely be a way to integrate the two at that time.
Awesome!
Closing based on discussion; happy to reopen if there's more to discuss.
Just a quick note: in this code sample from @noppa:
match(vector) { when ({ x, y, z, constructor: ^Vec3 }) { Math.hypot(x, y, z); } }
It would not work as Noppa intended, whether or not Vec3 had a [Symbol.matcher].
The proposed semantics of the "value match" operator is that if the value is a primitive, it checks equality (as if you'd subbed in the value literally to produce a primitive-matcher); otherwise, it invokes the custom matcher protocol if it can; otherwise it's a runtime error.
It works this way at least partially to avoid the possible confusion Noppa cites, where matching would work substantially differently based on whether the object defines its own protocol or not.
That said, an object-equality custom matcher is easy to define:
function IsClass(cls) {
return {
[Symbol.matcher]: val=>({matched: cls === val.constructor, value: val})
};
}
match(vector) {
when (^IsClass(Vec3) into {x,y,z}) { Math.hypot(x,y,z); }
}
Most helpful comment
I have not heard any feedback yet that it's too complicated for either.
JavaScript does not provide this feature, and as such, pattern matching can not provide it either.
A separate proposal (which is already in progress) would provide a mechanism for this, and there'd surely be a way to integrate the two at that time.