Switch expressions -- some revisions

Brian Goetz brian.goetz at oracle.com
Thu Dec 14 21:22:48 UTC 2017


After reviewing the feedback on the proposal for switch expressions, and 
a bit of going back to the drawing board, I have some proposed changes 
to the plan outlined in the JEP.


1.  Throw expressions.  While throw expressions are a reasonable 
feature, many expressed concern that if permitted too broadly (such as 
in method invocation context), they would encourage "tricky" code for 
little incremental expressiveness.  The real need here is for arms of 
expression switches to be able to throw when an unexpected state is 
encountered; secondarily it may be useful allow a value-bearing lambda 
to unconditionally throw as well.  But extending this to &&, ||, 
assignment, and method invocation context seems like asking for 
trouble.  So we'll narrow the treatment here, allowing throw on the RHS 
of a switch expression ARM, and possibly also the RHS of a lambda.  
(This doesn't close any doors on making `throw` an expression later, if 
desired.)


2.  Local return from switch.  In the proposal, we borrowed the 
convention from lambda to use "return" for nonlocal return, mostly on 
the theory of "follow the arrow".  But this is pretty uncomfortable, 
made worse by several factors: a) despite the syntactic similarity, we 
don't follow exactly the same rules for case arms of expression switches 
as for lambdas (such as treatment of captured vars), and b) when 
refactoring from statement switch to expression switch or vice versa, 
there's a danger that an existing "return" could silently swap between 
nonlocal and local return semantics.

So we dusted off an old idea, which we'd previously explored but which 
had some challenges, which is to use "break" with an operand instead of 
"return" to indicate local return in switch expressions.  So:

     int y = switch(abs(x)) {
         case 1 -> 1;
         case 2 -> 2;
         case 3 -> 3;
         default -> {
             println("bigger than 3");
             break x;
         }
     };

The challenge is ambiguity; this could be interpreted as a nonlocal 
break out of an enclosing loop whose label is `x`.  But then we realized 
that if `x` is both a variable and a label, we can just reject this, and 
tell the user to rename one or the other; since alpha-renaming the label 
is always source- and binary-compatible, the user has at least one (if 
not two) reasonable choices to get out of this problem.

The benefit here is that now "break" means basically the same thing in 
an expression switch as it does in a statement switch; it terminates 
evaluation of the switch, providing a value if one is needed.  Having 
addressed the ambiguity problem, I think this is a slam-dunk, as it 
aligns expression switch and statement switch quite a bit (same capture 
rules, same control flow statements.) We can also, if we like, support 
"break" for local return in lambdas (we should have done this in 8), to 
align the two.


3.  (Optional.)  There's room to take (2) farther if we want, which is 
to complete the transformation by eliminating the fake "block 
expression" in favor of something more like existing switch.  The idea 
would be to borrow from statement switches, and rewrite the above 
example as (note where we use colon vs arrow):

     int y = switch(abs(x)) {
         case 1 -> 1;
         case 2 -> 2;
         case 3 -> 3;
         default:
             println("more than 3");
             break x;
     };

So in this context, then "case L -> e" in an expression switch is just 
sugar for "case L: break e".  As with lambdas, I expect the 
statements+break form to be pretty rare, but we still need to have a way 
to do it (not all objects can be created in a single expression without 
resorting to stupid tricks.)

A good way to think about this is that this is leaving statement switch 
completely alone, and then expression switch "extends" statement switch, 
adding the nice arrow shorthand and the exhaustiveness analysis.  The 
downside is that expression switch is even more "infected" by existing 
switch semantics, but after thinking about it for a while, this doesn't 
bother me.  (It's more uniform, plus its considerably harder to make the 
"accidental fallthrough" mistake in an expression switch than a 
statement switch.)

I expect this proposal will be a little more controversial than (2) -- 
mostly because some are probably holding out hope that we'd radically 
rework existing switch -- but it has the major advantage of further 
building on existing switch, and also refrains from introducing a 
similar but different kind of fake block expression. Overall this is is 
more of a "build on what's there" solution, rather than "add something 
new in the gap."


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20171214/ea3c5f6b/attachment.html>


More information about the amber-spec-experts mailing list