-
Notifications
You must be signed in to change notification settings - Fork 211
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
Case expressions instead of if-case statement #2181
Comments
This syntax looks a little awkward. A suggestion of mine is to introduce the
|
It has a certain Shakespearean charm: if (music be TheFoodOfLove) playOn(); But I think overall it would look even stranger to most users. Also, pragmatically, |
Instead of: if (case [int x, int y] = json) return Point(x, y); The proposed syntax is now: if (json case [int x, int y]) return Point(x, y); This does *not* define an infix case *expression*. But it is more forward-compatible with that syntax if we decide to do that later. See #2181.
I have a PR out that changes the syntax of if-case statements to use the "infix-like" syntax described here. So instead of: if (case [int x, int y] = json) return Point(x, y); The proposed syntax is now: if (json case [int x, int y]) return Point(x, y); This change does not define an infix I made this change for three main reasons in increasing order:
|
The current proposed syntax unifies binders and matches into a single grammar. It also uses the same type inference process for pattern variable declarations and patters in if. That means there's less need to have a There's no longer something that looks like an infix |
After lots of agonizing and back and forth, we've gone back to a syntax where variable patterns in cases are slightly different from those in pattern variable declaration statements. Because of that, the previous It does not define |
This is a strawman for a more general
case
expression form to replace the narrowly targetedif-case
statement in the patterns proposal.Background
The main use for patterns in control flow is switch statements and expressions. However, those can be fairly verbose. Following Swift, the proposal also defines an
if-case
statement form:In an issue, @lrhn suggested that instead of
if-case
statements, we follow C# and Java and allow a pattern afteris
(orinstanceof
in the case of Java):Patterns would be allowed in any
is
expression, not just as a direct expression in an if condition, allowing useful chaining like:Unfortunately, allowing any matcher pattern after
is
would lead to some problems. Lasse and I spent some time discussing it and we came up with another idea, described here. If we decide we like this better, I'll roll it into the main patterns proposal and removeif-case
.Proposal
We define a new infix
case
operator. The left-hand side is an expression, and the right-hand side is a matcher pattern. The above examples look like:This is not restricted to use in if conditions. It's a general-purpose expression that can appear anywhere expressions are allowed:
It has the same precedence as
is
andis!
:(You can think of the existing
is
andis!
expressions as syntactic sugar for a subset of whatcase
expressions can match.)TODO: Should we allow guard clauses?
Control flow and scoping
The two key challenges with allowing a refutable pattern to appear in any expression context are:
These two questions are intertwined: if the pattern fails to match, we need to ensure that no code where the variables it binds are in scope can be executed.
Having a restricted
if-case
statement form instead of an expression form answers both of those. Since the pattern can only appear directly inside an if condition, the control flow behavior and scoping extent are fairly obvious. It's less obvious how an expression should behave.The insight is that there are some places in the grammar where a Boolean expression is expected in order to perform control flow. We call these refutable positions. The behavior of a
case
expression can vary depending on whether it appears in a refutable position or not.Refutable position case expressions
When a
case
expression is in a refutable position, then match failure causes it to jump over some specified code. Any variables thecase
expression binds are only in scope in that region.An expression is in a refutable position if it is:
if
statement or element.while
statement.?:
) expression.&&
expression.&&
expressionE
andE
is in a refutable position.(...)
expressionE
andE
is in a refutable position.Put together, these rules cover a series of
&&
appearing directly inside a condition expression, ignoring parentheses which have no effect. As in:When a
case
expression appears in a refutable position, variables bound by its pattern are in scope in any subsequent&&
operands as well as the region of code executed when the surrounding condition is true. For if statements, that's the then statement. For if elements, the then element, etc.If the pattern matches, then the
case
expression evaluates totrue
and execution proceeds. Otherwise, it evaluates tofalse
, any remaining operands in the&&
chain short-circuit, and the condition is `false.Non-refutable position case expressions
When a
case
expression is not in a refutable position, it is a compile-time error if the matcher binds any variables. This sidesteps any questions around scope. The result of thecase
expression istrue
if the pattern matches andfalse
otherwise.This lets users use
case
expressions in any place where it's useful to be able to ask questions about the structure of some object, while avoiding binding variables in arbitrary expressions and leading to confusing scope.Opinion
When I first started writing this up, I was hoping we could piggyback on the type promotion rules and basically say you can have variables in a
case
expression in all of the places where type promotion says a variable can show some type. Then the scope of those variables is the scope where the promoted variable has its promoted type.We'd get some conceptual unification and hopefully it would be easier for users to understand the scope since it follows rules they are already somewhat familiar with.
On reading the current flow analysis spec, I came to the conclusion that the flow analysis rules are much too subtle to hang variable scoping off of. Mirroring those would imply allowing code like:
Those look horrifically wrong to me even if they are technically sound.
Instead, I proposed the much simpler "refutable position" above which I think covers the cases we care about and has reasonable scoping rules. The result basically takes the current proposed
if-case
statement and:Tweaks the syntax to be "expression
case
pattern" instead of "case
pattern=
expression". I like this and would suggest doing that even if we keepif-case
as a dedicated statement.Extends it to be allowed in conditional expressions and while conditions. If-case elements are already planned, and this seems like a reasonable extension.
Supports chains of
&&
. This is reasonable, but it does look kind of strange especially if we allow variable bindings in preceding operands. It becomes a very odd special case rule where a chain of&&
directly inside a condition expression has some special powers.On further thought, I also don't find it particularly strongly-motivated either. Instead of:
If the right operand doesn't depend on the left then you can always write:
I think that's likely more idiomatic.
Adds a more or less unrelated infix
case
expression that can be used anywhere but can't bind variables. Kind of neat but not super valuable. Most examples I came up with felt kind of contrived and not much better than the expression you would write today instead.Overall, this didn't come together as well as I was hoping, but it has some promise, or at least pieces of it do.
The text was updated successfully, but these errors were encountered: