Skip to content
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

Could we re-evaluate what our "must-haves" are? #305

Open
rkirsling opened this issue Oct 5, 2023 · 9 comments
Open

Could we re-evaluate what our "must-haves" are? #305

rkirsling opened this issue Oct 5, 2023 · 9 comments

Comments

@rkirsling
Copy link
Member

It's so easy to get carried away with a pattern matching proposal, and I fear we're doing so again in the latest rewrite. Even though the intention was to increase comprehensibility by having "layers" to the proposal, I feel like we've instead ended up in a place where the proposal is harder to reason about, due to how a few of its pieces interact.

To start from some more positive territory:

  • I do want JavaScript to have a match statement and I think many of our proposed patterns are looking spiffy (objects, arrays, primitives, etc.)!

  • While it might trip me up a little that when foo: isn't an unconditional match, I can certainly appreciate explicit var/let/const as a JS-y approach to bindings (and it's super comforting to see that Dart did similarly!).

  • Other folks may have strong opinions about regexes and custom matchers, but I'll happily stand aside from those particular debates (and accordingly not mention them at all here 😄).

My discomfort has to do with three parts in particular: is, and, and if patterns.
That said, it's not really any one of these pieces on its own that's the core issue:

  • I get that if (x is { foo: Number }) would be neat to have. Do I think we should postpone it as Python did? Oh yes, definitely. But it's a reasonable thing for JS to have in the long run.

  • or is certainly a must-have (since we can't be "a better switch" if we can't join multiple cases together), but and is not—the thing we unequivocally need is a way to conjoin a binding with another subpattern, yet this is ironically the place where and feels like a confusing word choice. I would personally favor respelling that while following Python's lead in rejecting and (and not), in the interest of encouraging users to keep patterns simple.

  • Case in point, I can't help but feel that if patterns came from a line of thinking like, "Hey, now that we can and whatever we want to... 😈". Make no mistake, I think this is a bad idea and that we should stick to prior art; however, I will admit that "when if looks yucky!" on its own wouldn't be a very strong argument against it. 😅

The real crux of my discomfort is the interplay between these.
As such, there are multiple facets to it and various ways that it could be alleviated:

  • Having this proposal be not just a match statement but also an is expression means we now need to consider how patterns play with every other aspect of the language, which is incredibly challenging to reason about! Sticking to a match statement gives us freedom to do certain novel things within the confines of those braces—things like, say, and and if patterns!—so we might view is itself as a misstep. Still, Jordan argued that we'd want it sooner or later anyway, and I myself just admitted that the basic use case is pretty neat...so let's instead consider what's making me wish that is were something that "isn't".

  • Above all, the idea that if (...) could exist as two different constructs in adjacent contexts feels to me like a threat to the language itself. I don't want to live in a world where this is valid code:

    if (... is ... if (... is ... if (...)))
        if (...)
            return;

    Whether or not this can be fleshed out into "good" code, the issue I want to highlight is that we could no longer be certain at a glance what construct if (...) is when scanning code. Some ways this could be alleviated:

    • keep if as a guard instead of a pattern and exclude guards from is
    • respell if
    • require braces for is { ... } for clear visual delineation
  • Similarly, having all of or/and/not available for use right alongside ||/&&/! feels like we're bifurcating boolean expressions. I think Dart provides some really interesting food for thought here—they do have and patterns (spelled &&), but instead of a fully flexible is expression, they just have an if-case statement. I think I'd feel way more comfortable with is if it were specifically if-is! As such, possible alleviations include:

    • introduce an if-is statement and not a general-use if expression
    • jettison and/not (while respelling the let x and <pattern> case)
    • require braces for is { ... } for clear visual delineation

Anyway, I hope this serves as a clearer expression of my concerns. (I'm breathing much easier just having written it!) I understand that we're all coming at this from differing philosophies, and I don't expect everyone to suddenly agree to postponing is, dropping and, turning if patterns back into guards. I would just like us to take stock of which pieces are the most important and have a calm discussion about mitigating their compound effects. 🙇

@ljharb
Copy link
Member

ljharb commented Oct 5, 2023

There are a lot of places an and pattern and a not pattern are required for clarity; including that in JS (perhaps unlike in python? i'm not familiar with it) values can be many things at once - functions, objects, iterable, etc.

Similarly, it's definitely a requirement to have a way to apply arbitrary an JS rubric, otherwise it won't be possible to fully replace switch - it need not be spelled if, but something like it (that doesn't force creation of a function) is definitely necessary.

the issue I want to highlight is that we could no longer be certain at a glance what construct if (...) is when scanning code

i'm not sure why syntax highlighting wouldn't completely address this concern; it works fine for template literals in which any arbitrary JS could seemingly be nested?

right alongside ||/&&/! feels like we're bifurcating boolean expressions

||/&& are not boolean expressions or operators, they're value selection operators that use boolean logic.

@rkirsling
Copy link
Member Author

in JS (perhaps unlike in python? i'm not familiar with it) values can be many things at once

Right, I suppose this may make and a must-have for JS after all—Ron also gave when Map and { size: 0 } as an example. If so, then we wouldn't be respelling it either, but my queasiness about its usage alongside && (particularly for newcomers to the language) does remain. (FTR, I have no strong feelings about not.)

it's definitely a requirement to have a way to apply arbitrary an JS rubric

Sorry, what do you mean here?

||/&& are not boolean expressions or operators, they're value selection operators that use boolean logic.

I don't think this is serving to correct anything I was meaning to express though... 😅

@ljharb
Copy link
Member

ljharb commented Oct 5, 2023

I mean that I want to apply arbitrary logic to decide if something matches or not. Not every test is a simple one-argument predicate, nor something that patterns necessarily cover. Think of it like an escape hatch.

@rkirsling
Copy link
Member Author

Certainly, but I think that's still upheld by having if be a guard?

@ljharb
Copy link
Member

ljharb commented Oct 5, 2023

Somewhat - guards may need to utilize bindings from a pattern, and in deeply nested locations, so it's quite helpful to have them as patterns themselves.

@littledan
Copy link
Member

I like the reductions proposed in this thread. and in particular has been somewhat confusing when sharing pattern matching examples with people.

@dminor
Copy link

dminor commented Apr 11, 2024

I share the concerns that Ross raises here. I'd love to see the champions present on why a Python style match statement was considered for JavaScript and found to be not sufficiently powerful, because I'm not convinced we need anything more.

In particular, I think is should be shelved and revisited in a follow-on proposal.

@tabatkins
Copy link
Collaborator

When we tried to propose this without is, it was blocked by some members of the committee because it felt like too much new stuff just for a single new match construct. It's amusing/frustrating (but also expected) that we'd be getting feedback that with is it's too much new stuff and we should just add a single new match construct. ^_^


and is necessary for several expected-to-be-common use-cases:

  • Being able to test an object in two ways is straightforward and reads well, to my eyes - the when Map and {size: 0} example - and cannot be accomplished in any other way (without adding more special-case syntax that is either the same power as and or less powerful).
  • The other common use-case is using it to bind the value of a sub-pattern while testing it - when Map and let foo. This functionality exists in several pattern-matching languages, tho often with a special-case syntax: Haskell uses foo@pattern, for example. If we introduced a special-case syntax for this, the binding part would still need to use the full binding pattern syntax (let foo @ other-pattern, for example) so we knew the type of binding to use, and at that point it's syntactically identical to just using and, just spelled differently.

Python's rejection of and is based, notably, on the idea that you don't really need to run multiple distinct tests on a single value, and that is fairly reasonable for Python pattern-matching, which doesn't have any sort of custom matcher, and whose three major "structural" patterns - list patterns, dict patterns, class patterns - are largely disjoint in usage. These conditions are not true for this proposal - we have custom matchers, and our equivalent of the dict and class patterns (object patterns, and the built-in class custom matchers) overlap due to how JS syntax works.

(That said, Python is one of the languages that lacks the ability to both bind and further test a sub-value, so an & would have been useful there.)


For if, yeah, we could spell it differently, but the ability to execute a more arbitrary test inside of a pattern (especially without requiring the test to be in a closure) is useful; it takes the pressure off of us to have to stuff everything inside the pattern language. Doing it as a pattern (nestable inside other patterns) rather than a guard (run at the very end, after matching has otherwise been successful) means we can safely use or and have one of the clauses get skipped if it's something that fails the test. Without it, you have less control over which or clause we actually take bindings from; this might require you to not use an or at all, and instead break it into several match arms with repeated value expressions.


For the spelling of and/or, the only reason we're spelling them like that right now is due to some early pushback from the committee that didn't want || and && used with slightly different meanings from their use in general expressions. (And |/& were right out, as they diverged even further.) That said, I don't think any of the champions cares that much about this, and we'd be willing to spell these operators whatever way is desired, if that is a committee sticking point.

@Jack-Works
Copy link
Member

Jack-Works commented Apr 16, 2024

When we tried to propose this without is, it was blocked by some members of the committee because it felt like too much new stuff just for a single new match construct.

Some addition (as I understand), because they think pattern syntaxes are too much, but only can be used in the match construct (is a waste), they hope to be able to use patterns in more places to worth it

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

6 participants