You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When we took over this proposal, we changed the original grammar of the match arms from <pattern> => <expr>; to when(<pattern>): <expr>;, in part to allow if() guards to be placed alongside the when(), like when(...) if(...): <expr>. We no longer have if() guards (rather, if() is just a pattern syntax now), and so we removed the required parentheses from when() - the arms are now defined as when <pattern>: <expr>;.
I think the when keyword has become vestigial here, tho. There's no longer a grammatical requirement for it, and it makes some types of simple patterns, like when if(...): ...;, read somewhat silly.
Further, most languages with pattern-matching features don't have a per-clause introductory keyword (or other syntax) either: in particular, Rust, Haskell, and C# (in some cases) don't. (Python and Dart do, as does C# in some cases - they all happen to use case - so the ecosystem is mixed on the matter.)
We should just drop the when keyword and make the syntax slightly lighter. This would resolve @rkirsling's objection about when if(...) looking silly. However, @ljharb appears to have an objection to this, so let's discuss.
I'm not stuck on how when is spelled, but I definitely think it's critical for readability, teachability, googleability, and learnability to have some kind of clear syntactic marker for the beginning of a clause.
Reacted by tukkek, Matthew McCune, Dominik Scholz, leah and Lino Le Van
I think readability/learnability is already completely handled by the fact that the only reasonable way to format a match is with each arm on a line. If someone does anything else, that's them shooting their own foot. We're not providing a footgun, they're just pointing downwards and pulling the trigger, and that's on them. It's the same as literally any other sequences of statements - if you're putting multiple statements on a line, you're either hurting yourself or you have a very good personal reason for formatting it that way.
In all other cases, your match clause starts on a new line. You see a new line, it's a new match clause.
For googleability, we already have the match name itself. I know you wanted to avoidcase and switch to avoid crossed wires, but do we really need two keywords?
Reacted by Bo Lingen, Muthu Kumar, Eric Summers and Dominik Scholz
It's an expression, tho. If it's multiline, you indent; that's how everyone writes their code already (or, again, they're just hurting themselves for no reason and we can't stop them). This is true if we have do-exprs too - the obvious formatting for do-exprs will indent their contents.
Basically, do you think JS made a mistake in its general syntax, having statements not require an introductory keyword? Because that's the exact same situation, as far as I can tell. There's no difference between:
At least, as far I can tell. If you think there is a difference in these two cases, could you elaborate on that? Or if my reasoning is off-base at a different point?
Those two cases are very different; the former is a series of top-down instructions, and the latter is a pattern to expression map.
So what about a plain object, which would have the same structure lacking prefix keywords? I think it would be reasonable to mirror that considering it's also a map of sorts.
Those two cases are very different; the former is a series of top-down instructions, and the latter is a pattern to expression map.
Yes, I know they're different syntax constructs. My question is: for the purpose of reading, writing, and understanding code, how are they different? What makes the one acceptable without a prefix, while the other needs one?
If you write if(...) { foo; bar; baz } it's just as potentially difficult to read as match(...) { f: foo; b: bar; b2: baz; }. And in both cases, if you write them properly, one statement/arm per line, and use indentation to visually indicate that you're breaking a long statement/arm across multiple lines, it's perfectly readable and understandable without a prefix.
So I'm asking you to elaborate on what, exactly, you feel is the problem being solved by requiring a prefix on arms, and why that same problem doesn't apply in other cases like this.
I’m saying that they’re conceptually unrelated, despite syntactic similarity.
I think that “what’s proper” is not an objective thing - it’s something the community discovers over time. Additionally, even long time practitioners of the language can have preferences that violate the community’s preferences. It’s not for us to deem whether separate lines is “proper”, that’s paternalistic - it’s certainly how I’d hope to write it. Our job is to design things that we think will be used and written best, and to ensure that other patterns either are impossible, or are minimally problematic. I personally believe that prefixes will help with readability, syntax highlighting, parsing, linting, googling/education/learnability, and, yes, starting each clause on its own line.
This example is hard to parse visually, despite being formatted pretty well. I don't like that we have lines starting with braces for one thing, normally that would only happen inside of an object literal. Additionally, it feels really awkward for the colon separator to just sit between two things that kind of look like expressions or objects or... who knows what. It looks like ternary soup with missing question marks. Furthermore, there is no clear indication in the syntax that these lines are special, someone unfamiliar with this new syntax would likely expect that each line runs in order as statements would, not that it would terminate at one of them.
Then there is the dangling default:, do you still use that key word without the when? Do you just put one last statement with nothing preceding it? Do you do : expression;? Seems really weird.
This is more clear, the when keyword clearly indicates something special is going on, and it looks suspiciously similar to the familiar switch statement. Furthermore, even if I've never seen this construct, I can generally get the idea of what it does. Without the keyword, I would be scratching my head wonder what those lines did, and how someone defined a function called match without the function keyword. Even assuming my IDE colors it differently and I know it must be a special language construct, it's still very confusing exactly what it does.
Regarding when if(...): ...;, is there a reason that the when needs to be used in that special case?
Basically, why not allow the lines to start with if while still requiring when in all other cases? Essentially the match statement can have either matchers or conditions, and these would both occupy the same syntactic region.
Though that brings up a question I'm having a hard time finding an answer to in the proposal: Why not just support boolean expressions in the patterns region?
Yeah, patterns are a very distinct syntax from expressions, and can't be arbitrarily mixed. The one place we currently allow them (inside the if() pattern) has a reliable indicator (the if) and boundary (the parens) that let us handle it. Your suggestion just uses parens, which isn't a strong indicator; parens can also be wrapped around ordinary patterns, so we can't tell how we're supposed to parse it ahead of time.
This example is hard to parse visually [snip]
As far as I can tell, every problem you raise here except the first is exactly as present if you use when at the start. We still use a : between the pattern and the expression, it looks exactly as much like ternary, and someone unfamiliar with the syntax still wouldn't know what they were looking at (but I expect that, in either syntax, they could figure out the gist pretty easily, as many languages have pattern-matching).
As for the first, we do have lines starting with {} elsewhere in the language: destructuring patterns. Pattern-matching patterns are, intentionally, closely aping destructuring patterns, as they do very similar things. The intuition should carry over.
Then there is the dangling default:, do you still use that key word without the when?
Yes, you still write default: in either syntax variant.
Regarding when if(...): ...;, is there a reason that the when needs to be used in that special case?
Syntax special cases are generally a bad design. It means that, if you wanted to later edit that condition to add an or {foo}, or just swap the if() for a different condition, you have to remember to go back and add a when to the beginning, too. They can be justified if it's particularly common, or the result without the special case is particularly egregious, but we avoid it when we can. (But, luckily, going when-less avoids the problem entirely.)
I think you misunderstand half my issues with it. My point is that with the when there is a clear signal that something special is going on, which contextualizes the rest of the syntax. Furthermore, with the when the entire statement look very similar to a switch, the over all structure is almost identical:
switch(<subject>) {case<value1>: <expressions1>;
case <value2>: <expressions2>;
case <value3>: <expressions3>;
default: <defaultExpression>;
}match(<subject>) {when<pattern1>: <expressions1>;
when <pattern2>: <expressions2>;
when <pattern3>: <expressions3>;
default: <defaultExpression>;
}
This results in something familiar, even if the shape of a pattern is complicated and confusing. Also, one could just as easily argue that switch should work like this:
Is there really a reason why there must be a keyword case there? But there is, so if for no other reason than symmetry with switch I would prefer match include the when keyword.
As for the first, we do have lines starting with {} elsewhere in the language: destructuring patterns. Pattern-matching patterns are, intentionally, closely aping destructuring patterns, as they do very similar things. The intuition should carry over.
Yes, we have { a, b, c } = object; but typically you would do it let { a, b, c } = object; (or const). Destructuring into existing variables also looks very weird.
Typically I'm all for making things concise, but I think it goes a step too far here given how complicated the grammar already is. More signals to explain what's going on greatly benefit readability.
Syntax special cases are generally a bad design.
I'm not sure that I agree with this statement as a generality. Syntax special cases that lead to weird esoteric grammar aren't great, but special cases that work intuitively (as I would argue this one would) are generally beneficial.
There's no reason that when if ( and if ( couldn't both work if you like. But I'm not convinced by the refactoring argument, particularly since it seems that the convention would be for the if to come last meaning you likely would add when { foo } or to the start of the line anyway unless the order of the short-circuit logic was important. In that case, you would just have the when if construct. That, or the special case could treat a starting if ( ) as equivalent to when if ( ) and permit if ( ) or { foo } to just work. I dislike the match lines not starting with a keyword, but if the keyword is if that's just as good as it being when. It's still probably better to re-order the statement unless the order is critical though.
Activity
ljharb commentedon Oct 4, 2023
I'm not stuck on how
whenis spelled, but I definitely think it's critical for readability, teachability, googleability, and learnability to have some kind of clear syntactic marker for the beginning of a clause.tabatkins commentedon Oct 4, 2023
I think readability/learnability is already completely handled by the fact that the only reasonable way to format a match is with each arm on a line. If someone does anything else, that's them shooting their own foot. We're not providing a footgun, they're just pointing downwards and pulling the trigger, and that's on them. It's the same as literally any other sequences of statements - if you're putting multiple statements on a line, you're either hurting yourself or you have a very good personal reason for formatting it that way.
In all other cases, your match clause starts on a new line. You see a new line, it's a new match clause.
For googleability, we already have the
matchname itself. I know you wanted to avoidcaseandswitchto avoid crossed wires, but do we really need two keywords?ljharb commentedon Oct 5, 2023
Given that the expression on the RHS will be multiline, and that's perfectly reasonable, I don't think this holds.
tabatkins commentedon Oct 5, 2023
It's an expression, tho. If it's multiline, you indent; that's how everyone writes their code already (or, again, they're just hurting themselves for no reason and we can't stop them). This is true if we have do-exprs too - the obvious formatting for do-exprs will indent their contents.
Basically, do you think JS made a mistake in its general syntax, having statements not require an introductory keyword? Because that's the exact same situation, as far as I can tell. There's no difference between:
and
At least, as far I can tell. If you think there is a difference in these two cases, could you elaborate on that? Or if my reasoning is off-base at a different point?
ljharb commentedon Oct 5, 2023
Those two cases are very different; the former is a series of top-down instructions, and the latter is a pattern to expression map.
rkirsling commentedon Oct 5, 2023
Am I remembering correctly that you'd object to respelling
whenascase, @ljharb?ljharb commentedon Oct 5, 2023
@rkirsling yes, but i'm open to almost any other respelling :-) I agree
when ifreads weirdly.haltcase commentedon Oct 5, 2023
So what about a plain object, which would have the same structure lacking prefix keywords? I think it would be reasonable to mirror that considering it's also a map of sorts.
ljharb commentedon Oct 5, 2023
@haltcase keying into a plain object is a map of sorts, sure - an object by itself isn't inherently like a match construct.
tabatkins commentedon Oct 16, 2023
Yes, I know they're different syntax constructs. My question is: for the purpose of reading, writing, and understanding code, how are they different? What makes the one acceptable without a prefix, while the other needs one?
If you write
if(...) { foo; bar; baz }it's just as potentially difficult to read asmatch(...) { f: foo; b: bar; b2: baz; }. And in both cases, if you write them properly, one statement/arm per line, and use indentation to visually indicate that you're breaking a long statement/arm across multiple lines, it's perfectly readable and understandable without a prefix.So I'm asking you to elaborate on what, exactly, you feel is the problem being solved by requiring a prefix on arms, and why that same problem doesn't apply in other cases like this.
ljharb commentedon Oct 16, 2023
I’m saying that they’re conceptually unrelated, despite syntactic similarity.
I think that “what’s proper” is not an objective thing - it’s something the community discovers over time. Additionally, even long time practitioners of the language can have preferences that violate the community’s preferences. It’s not for us to deem whether separate lines is “proper”, that’s paternalistic - it’s certainly how I’d hope to write it. Our job is to design things that we think will be used and written best, and to ensure that other patterns either are impossible, or are minimally problematic. I personally believe that prefixes will help with readability, syntax highlighting, parsing, linting, googling/education/learnability, and, yes, starting each clause on its own line.
zeel01 commentedon Oct 4, 2024
I like the
whenin general as it better differentiates this from other types of block-constructs and keeps it parallel toswitch.My concern is this example from the main document, when written without
when:This example is hard to parse visually, despite being formatted pretty well. I don't like that we have lines starting with braces for one thing, normally that would only happen inside of an object literal. Additionally, it feels really awkward for the colon separator to just sit between two things that kind of look like expressions or objects or... who knows what. It looks like ternary soup with missing question marks. Furthermore, there is no clear indication in the syntax that these lines are special, someone unfamiliar with this new syntax would likely expect that each line runs in order as statements would, not that it would terminate at one of them.
Then there is the dangling
default:, do you still use that key word without thewhen? Do you just put one last statement with nothing preceding it? Do you do: expression;? Seems really weird.The original exaple:
This is more clear, the
whenkeyword clearly indicates something special is going on, and it looks suspiciously similar to the familiarswitchstatement. Furthermore, even if I've never seen this construct, I can generally get the idea of what it does. Without the keyword, I would be scratching my head wonder what those lines did, and how someone defined a function calledmatchwithout thefunctionkeyword. Even assuming my IDE colors it differently and I know it must be a special language construct, it's still very confusing exactly what it does.Regarding
when if(...): ...;, is there a reason that thewhenneeds to be used in that special case?Basically, why not allow the lines to start with
ifwhile still requiringwhenin all other cases? Essentially the match statement can have either matchers or conditions, and these would both occupy the same syntactic region.Though that brings up a question I'm having a hard time finding an answer to in the proposal: Why not just support boolean expressions in the patterns region?
I assume I'm missing a reason why this can't be allowed?
ljharb commentedon Oct 4, 2024
it would be confusing to conflate pattern space and JS expression space, imo.
tabatkins commentedon Oct 4, 2024
Yeah, patterns are a very distinct syntax from expressions, and can't be arbitrarily mixed. The one place we currently allow them (inside the
if()pattern) has a reliable indicator (theif) and boundary (the parens) that let us handle it. Your suggestion just uses parens, which isn't a strong indicator; parens can also be wrapped around ordinary patterns, so we can't tell how we're supposed to parse it ahead of time.As far as I can tell, every problem you raise here except the first is exactly as present if you use
whenat the start. We still use a:between the pattern and the expression, it looks exactly as much like ternary, and someone unfamiliar with the syntax still wouldn't know what they were looking at (but I expect that, in either syntax, they could figure out the gist pretty easily, as many languages have pattern-matching).As for the first, we do have lines starting with
{}elsewhere in the language: destructuring patterns. Pattern-matching patterns are, intentionally, closely aping destructuring patterns, as they do very similar things. The intuition should carry over.Yes, you still write
default:in either syntax variant.Syntax special cases are generally a bad design. It means that, if you wanted to later edit that condition to add an
or {foo}, or just swap theif()for a different condition, you have to remember to go back and add awhento the beginning, too. They can be justified if it's particularly common, or the result without the special case is particularly egregious, but we avoid it when we can. (But, luckily, goingwhen-less avoids the problem entirely.)zeel01 commentedon Oct 4, 2024
I think you misunderstand half my issues with it. My point is that with the
whenthere is a clear signal that something special is going on, which contextualizes the rest of the syntax. Furthermore, with thewhenthe entire statement look very similar to aswitch, the over all structure is almost identical:This results in something familiar, even if the shape of a pattern is complicated and confusing. Also, one could just as easily argue that switch should work like this:
Is there really a reason why there must be a keyword
casethere? But there is, so if for no other reason than symmetry withswitchI would prefermatchinclude thewhenkeyword.Yes, we have
{ a, b, c } = object;but typically you would do itlet { a, b, c } = object;(orconst). Destructuring into existing variables also looks very weird.Typically I'm all for making things concise, but I think it goes a step too far here given how complicated the grammar already is. More signals to explain what's going on greatly benefit readability.
I'm not sure that I agree with this statement as a generality. Syntax special cases that lead to weird esoteric grammar aren't great, but special cases that work intuitively (as I would argue this one would) are generally beneficial.
There's no reason that
when if (andif (couldn't both work if you like. But I'm not convinced by the refactoring argument, particularly since it seems that the convention would be for the if to come last meaning you likely would addwhen { foo } orto the start of the line anyway unless the order of the short-circuit logic was important. In that case, you would just have thewhen ifconstruct. That, or the special case could treat a startingif ( )as equivalent towhen if ( )and permitif ( ) or { foo }to just work. I dislike the match lines not starting with a keyword, but if the keyword isifthat's just as good as it beingwhen. It's still probably better to re-order the statement unless the order is critical though.ljharb commentedon Oct 5, 2024
@zeel01 i was responding only to
The
whendenotes pattern space, theifdenotes expression space, and this distinction is necessary and desirable.zeel01 commentedon Oct 5, 2024
@ljharb I was only responding to @tabatkins.