Skip to content

Explicit syntax for irrefutable matches? #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
theScottyJam opened this issue Jul 16, 2021 · 3 comments
Closed

Explicit syntax for irrefutable matches? #213

theScottyJam opened this issue Jul 16, 2021 · 3 comments

Comments

@theScottyJam
Copy link
Contributor

This is a continuation of a conversation that started in this issue.

The issue is that there's a discrepancy between destructuring and pattern matching that may confuse developers. When destructuring, any non-existent properties are set to undefined by default (as opposed to throwing an error), while when pattern matching, non-existent properties cause the pattern to fail.

const { x } = {} // ok
match ({}) { when ({ x }) { /* Fail! */ } }

This discrepancy causes issues, such as the following:

  • Developers who come into Javascript after pattern-matching is introduced may start expecting destructuring to throw if a given property was not found in the target object - it would be a natural thing to expect given that's how pattern matching behaves.
  • When using default assignment and a property does not exist, does the pattern fail or does it receive a default value? Either option seems reasonable, and will likely become a source of confusion for developers. See Is default assignment allowed during pattern matching? #212 for this issue.

Here's one solution, that's similar to what was proposed in the previous discussion (but tweaked a bit). We make the identifiers from syntax such as { x } and [x] by default create bindings and not do any matching. To do an irrefutable match, you must add a "!" after the identifier.

when ({
  a, // `a` gets set to undefined if it does not exist
  b!, // `b` must exist on the target object
  c: C,
  d: D!,
  e: { x } as E,
  f: { y } as F!,
  // Example intuitive solution to the default assignment issue (issue #212)
  g = 2, // e gets set to 2 if it does not exist on the object, or if its undefined
  h! = 2, // f gets set to 2 only if it was explicitly set to undefined
})
when ([
  a,
  b!,
])

Note that a future proposal could add the "!" into the destructuring syntax as well:

const { x! } = {} // Throws
const { x } = {} // ok

One criticism @hax brought up previously was the fact that we're making the default behavior of { x } less useful. I would argue that there's plenty of use cases for binding potentially non-existent values out there, so it would be good to provide some sort of way to do so, and, I don't think it's that bad to get into the habit of using "!" after your irrefutable matches - it's not a lot of extra baggage. But, this is still an important point to consider and I can certainly see where @hax is coming from.

@ljharb
Copy link
Member

ljharb commented Jul 16, 2021

The problem is that with pattern matching, there’s not nearly as many cases for “binding but not detecting existence” as there is for “only match on existence”. Syntax should privilege the common, and pattern matching-related, case.

@treybrisbane
Copy link

Having read good chunks of this discussion over the couple of issues it's come up in, I think I understand where you're coming from.

But... Personally, I disagree that this is a problem.

I see destructuring as a short-form of potentially-nested property access and binding. It doesn't do any checking of object structure in the same way ordinary property access doesn't. const { x } = {}; doesn't throw an error due to x not being present on {} because const x = {}.x; doesn't.

On the other hand, I see pattern matching as a construct whose primary purpose is checking of object structure. Because of this, my default expectation is that match ({}) { when ({ x }) { 'match' }; else { 'no match' } } would result in 'no match' precisely because I see it as ('x' in {}) ? 'match' : 'no match'.

If I understand your proposal correctly, you're effectively arguing for when ({ x }) { ... } to succeed for literally any input. That would go completely against my intuition and expectations for how pattern matching would work.
IMO the discrepancy you're referring to is precisely what makes pattern matching, well, pattern matching.

@theScottyJam
Copy link
Contributor Author

You know ... I think I've got to agree with you two. when ({ x }) feels like a footgun if it'll match any object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants