Now the do expression semantics bans the following code:
const x = do {
for (const y of z) y.work()
}
Since pattern matching is using semantics of do expression in the RHS, this restrictions also applies to the match expression.
// SyntaxError!
const x = match(z) {
when([x, ...y]) {
for (const q of y) q.work()
}
}
But this restriction does not make sense if the match expression is used as an ExpressionStatement and its Completion Value is not collected by another MatchExpression or DoExpression.
// Also SyntaxError but doesn't make sense
function handle(z) {
when([x, ...y]) {
for (const q of y) q.work()
// Also SyntaxError, but why?!
}
}
We should make it valid.
I think it would be very confusing, and violate Tennant's Correspondence Principle, if the location of the match expression altered its semantics.
It simply doesn't make any sense to do that, because it _is_ an expression, and it's also a do expression, where that's banned.
Ehhhhhhhhhh. I think the argument from TCP gets much stronger if you're actually using the do keyword, which this proposal seems not to be.
do expressions ban this specifically because we want to prevent someone from observing the completion value of a loop. But if the match is in statement position, you don't observe that value. (That also applies to do in statement position, of course, but there is no reason to ever do that, so the restriction doesn't arise in quite the same way.)
This is what a thing that switch statement can do (and isn't a foot gun but a totally reasonable needs) but the match expression cannot.
switch (a) {
case b: {
for (const each of a) each.work()
break;
}
}
I don't think the restriction is necessary if is completion value is not used.
But if the match is in statement position, you don't observe that value.
Unless the match expression is the last ExpressionStatement in a do expression (so we need to cover that in the do expression).
It remains an open question if this syntax will look like when (…) { … } or when (…) do { … } or when (…) -> { … } or something entirely different.
It seems clear to me that if the keyword do is used, it must match the semantics of a do expression _precisely_, regardless of the position of the overarching match construct.
While it may be achievable and defensible (in the case we pick a syntax that omits "do") to make the RHS have "block" semantics instead of "do expression" semantics when the overarching construct is "an ExpressionStatement that is not inside a do expression, or the last statement in eval", but I am very unconvinced that this is a) worth the complexity or b) even desirable in the first place.
It seems clear to me that if the keyword do is used, it must match the semantics of a do expression precisely, regardless of the position of the overarching match construct.
I agree.
even desirable in the first place.
Of course, switch can do that and it's not a bad thing.
Or maybe we add a new MatchStatement with almost the same MatchExpression which allows a fully functional block instead of do expression in that position
That'd certainly be a less complex way to specify it - but it seems like it'd still be incredibly confusing for users.
I can think of function and class declarations' hoisting behavior (which only changes their availability, but not their semantics), and object literals vs blocks (which are a huge source of confusion). what other code has different semantics in expression vs statement position?
That'd certainly be a less complex way to specify it
Or add a new context flag, like ~CompletionValueCollected to specify the different behavior in a different context?
sure, but "how to write the spec"'s not the important part :-) i think it'd be a source of confusion to programmers.
so let's use the explicit do keyword. programmers will know it is the same as do expression does
That's a strong argument for picking one of the options that uses do (or whatever keyword do expressions end up with) in #181, but remains an argument to close this issue, so we match do expression semantics (whatever they end up being). Thoughts?
Do you mean the async match part?
all RHS's with do would have to implicitly change into async do semantics (this seems bad)
Actually, I don't think it is a problem. async match should start a new await context and reject return break continue just like async do does. And normal do expression in the async match block will inherit the await context naturally so do (not async do) in a async match is totally good
That's an opinion I'm coming around to, the more we all talk it out.
Most helpful comment
Ehhhhhhhhhh. I think the argument from TCP gets much stronger if you're actually using the
dokeyword, which this proposal seems not to be.doexpressions ban this specifically because we want to prevent someone from observing the completion value of a loop. But if thematchis in statement position, you don't observe that value. (That also applies todoin statement position, of course, but there is no reason to ever do that, so the restriction doesn't arise in quite the same way.)