tc39 / proposal-pattern-matching

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

Complete matching of array vs partial matching of object #243

Closed Pyrolistical closed 2 years ago

Pyrolistical commented 2 years ago

Correct me if I am wrong, but the array pattern requires a complete match whereas object pattern only requires a partial match.

array pattern matches completely

match ([1, 2]) {
  when ([x]): // does not match
  when ([x, y, z]): // does not match
  when ([x, y]): // matches
  when ([x, ...rest]): // would match if not for previous case
}

object pattern matches partially

match ({x: 1, y: 2}) {
  when ({x}): // matches
  when ({x, y, z}): // does not match
  when ({x, y}): // would match if not for first case
  when ({x, ...rest}): // would match if not for first case
}

Isn't this going to be confusing? Doesn't this violate the principle of least surprise? Wouldn't it be more consistent and less surprising if object pattern required a complete match?

if object pattern were to require a complete match

match ({x: 1, y: 2}) {
  when ({x}): // does not match
  when ({x, y, z}): // does not match
  when ({x, y}): // matches
  when ({x, ...rest}): // would match if not for previous case
}

It then begs the question on what it means to "completely match an object". I believe the principle of least surprise would be to require objects to match all enumerable properties.

ljharb commented 2 years ago

Lists have a length, objects don't. Requiring a complete object match would be extremely confusing - because there's also [[Prototype]] and inherited keys.

Additionally, only limiting it to enumerable properties (which is not the way destructuring works) would be quite confusing as well.

Pyrolistical commented 2 years ago

How does [[Prototype]] and inherited keys make it more confusing? They are part of the object being matched and would be consumed by ...rest would it not?

What does matching of enumerable properties have to do with destructuring? Destructuring is syntactic sugar as far as I am concerned. I understand destructuring is baked into this proposal and there is no alternative syntax, but I don't see it would cause confusing if a complete object match is required. Can you provide an example on where it would be confusing?

ljharb commented 2 years ago

no, ...rest only consumes own enumerable properties, just like in destructuring - but you can match against inherited and/or non-enumerable properties.

tabatkins commented 2 years ago

Yup, objects (and their relevant set of keys) are just fundamentally different from arrays in ways that make exact match more or less impossible, or at least extremely undesirable and confusing. The difference in behavior does hold its own small possibility of confusion, but exact-matching array length is common among pattern-matching constructs in many languages, because it's useful and fairly intuitive.

Limiting object matchers to own keys (or worse, enumerable own keys) would be extremely confusing and bad; properties coming from the prototype would fail to match. (Limiting solely to enumerable keys would also fail to expose properties that clearly exist.) And if you allow prototype properties, then you can't exact-match; all the methods are now visible, all the way up to Object.

And like Jordan said, all of this would be divergent behavior from destructuring. We are intentionally keeping array/object patterns as close to destructuring behavior as reasonably possible, with only very well-chosen divergences. If you can do const {x} = someObjWithLotsOfProps, but you can't match the same against a when ({x}), that's gonna be bad and confusing.

Pyrolistical commented 2 years ago

Interesting, thanks for explaining. The crux of this issue seems to be the desire to use destructuring as part of the pattern, which then it is more consistent to do a partial object match.

We wouldn't need to adhere to the destructuring behaviour if the pattern was independent of extracting values out of the pattern. IE. Require the with construct to use destructuring when (pattern) with (destructuring): consume destructured values.

This is exactly how regex pattern works.

when (/(\d+) \* (\d+)/) with ([_, left, right]): process(left, right);

But that would move the entire proposal in a different direction.