tc39 / proposal-pattern-matching

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

Move irrefutable/binding matchers to a different syntax #283

Closed tabatkins closed 6 months ago

tabatkins commented 2 years ago

(This is part of several issues stemming from my discussion with Yulia.)

Currently, the "plain ident" part of the matcher syntax space is taken by irrefutable matchers (except for a small carve-out for some literal constants). That is, in when([null, a]), the item is in the array matcher is a literal matcher for null, and the second always succeeds and binds the value to a.

This syntax is pretty nice because it means that, for many simple patterns, we look identical (and act nearly identical) to destructuring - [a, b] binds the first two values out of an array to a and b in both contexts. (Patterns just also do a length-check, which destructuring doesn't.)

However, in more complex cases it's not quite as obvious. When binding a value and doing further matching, the current spec uses and, like [a and ("foo" or "bar")] to pass the value to both the irrefutable matcher (to produce a binding) and the other matcher. While I find this pretty reasonable, multiple people have expressed confusion about this syntax pattern.

As well, this is very valuable syntax real estate. Yulia wants to be able to invoke simple function-based custom matchers (see #282) with as little syntax weight as possible, but right now you have to use the ${} interpolation matcher. Ron wants to be able to use this space for their Extractors proposal; it's not technically clashing (Extractor patterns require a trailing () or {}) but it's weird to occupy the same space.

Finally, several people have expressed misgivings with the current proposal's carve-out of a few literal values - Infinity and undefined are both technically valid bindings that you are allowed to assign to, but we recognize them as literal matchers instead of irrefutable matchers.


I think we can address all of the above by moving irrefutable matchers to a slightly different syntax. Specifically, I propose:

Avoiding more expression syntaxes both keeps us away from grammar issues, and leaves us more breathing room for additional patterns, like Extractor Patterns or similar using Foo().

ljharb commented 2 years ago

I don't think we should conflate additional meanings onto var/const/let; as works well.

How would this work for negative zero? Currently, the pattern -0 matches only -0, +0 matches only +0, and 0 matches both, which is quite convenient.

tabatkins commented 2 years ago

We'd lose the tri-state unless we special-cased it. Without a special case, we either keep the current semantics that'll recognize -0 and +0 as different (and 0 only matches +0), or we switch down to === semantics (so all of them are equivalent and recognize either zero).

In either case you could recover the lost behavior with functions if we accept #282, and combined with this issue they're easily invoked like when(posZero) or when(eitherZero), for the rare cases when the distinction is relevant.

ljharb commented 2 years ago

I would be pretty opposed to losing that tri-state zero comparison. Functions like Object.is certainly exist to handle it, but that's much less ergonomic and robust.

tabatkins commented 2 years ago

(Fwiw, I think the more reasonable situation would be to drop down to ===; the default pattern should match either zero, which the current tri-state achieves, and we should maintain that.)

I'm personally okay with maintaining the special case for +0 and -0 literals, fwiw.