tc39 / proposal-pattern-matching

Pattern matching syntax for ECMAScript
https://tc39.es/proposal-pattern-matching/
MIT License
5.51k stars 89 forks source link

let and relationship to extractors #314

Closed littledan closed 8 months ago

littledan commented 10 months ago

If I understand correctly where your current draft is (#313), then it looks like let is required to establish bindings within patterns. I definitely see how this is conceptually cleaner and more composable than earlier forms, though it's a bit syntactically heavy. Is this the champion group's current direction?

My understanding is that let is important to subsume the need for ${}, which was a way to distinguish between what's establishing a new binding, and what refers to literals to be matched, extractors, or other things to be read from outside of the pattern.

However, extractors show another path to distinguish between the pattern and the bindings--if it has parens after it, it's an extractor; otherwise it's a variable being assigned to. Would it work for pattern matching to apply the same mechanism here? This could be nice for consistency between forms as well as terseness, though it would reduce the other aspects of consistency which let introduces.

ljharb commented 10 months ago

I don't think it would, because we have a need to use the already available and massive corpus of predicate functions.

littledan commented 10 months ago

Could you elaborate on that? I don't understand how you want to embed predicate functions and how this relates to let.

tabatkins commented 10 months ago

Two important use-cases we want to ensure work as easily as reasonably possible are (1) matching against the value of an existing variable, and (2) matching using an existing predicate function (rather than requiring predicates to be specifically written against the pattern-matching model, using [Symbol.customMatcher]/etc.

Both of these compete for the "plain ident" syntax space with bindings, and since we have the "well do you want let or const or var" problem anyway, the easiest path forward is put bindings behind the declaration prefix, and let vars/preds use plain idents (along with custom matchers, which is how predicates are supported anyway (a built-in Symbol.customMatcher property on Function.prototype)).

(Technically, this collides these use-cases; you can't compare the subject with an object that has a custom matcher on it. In practice, this is pretty unimportant; comparing functions by object identity is a rare pattern. You can drop down to if() patterns when it's actually required.)

littledan commented 10 months ago

It'd be great if someone could give an example of using a predicate function, showing how the syntax differs from matching against equality of a value that's in a variable.

tabatkins commented 10 months ago
const CR = 0x0d;
const LF = 0x0a;
function isWhiteSpace(ch) {...}

match(someChar) {
    CR or LF: doSomething();
    isWhiteSpace: doSomethingElse();
    default: doAThirdThing();
}
littledan commented 10 months ago

Thanks for the example. Yes, I agree that these are important. Personally, I didn't mind the ${ } syntax to handle that case, though I understand that the rest of the committee strongly disliked it. Another option is to have some particular kind of syntax for this case, of matching against a variable which is a predicate, or something to be compared for equality. For example, with the placeholder keyword kw (since I don't have a good name in mind):

match(someChar) {
    (kw CR) or (kw LF): doSomething();
    kw isWhiteSpace: doSomethingElse();
    default: doAThirdThing();
}
Jack-Works commented 10 months ago

in this case, we have this (not as a group consensus but mentioned in the drafted spec):

match(someChar) {
    (=== CR) or (=== LF): doSomething(); // "===" pattern
    isWhiteSpace: doSomethingElse(); // custom matcher
    default: doAThirdThing();
}