Switch expressions -- some revisions

Brian Goetz brian.goetz at oracle.com
Thu Dec 14 22:45:58 UTC 2017

While we cannot always select the target type early, we always know 
whether the switch is in an value-bearing (expression) or void 
(statement) context.  So we can require that all breaks have the 
appropriate polarity, and properly validate whether -> is allowed, even 
before we know the types.

You could say that the arms themselves have a type, but that's mostly 
only useful for type-checking that the arms are consistent with the type 
of the switch.

There are still differences between the two forms (such as 
exhaustiveness requirements), so calling a statement switch a "void 
expression switch" might still be a bit of a stretch, but it may help as 
a way of understanding what's going on if you don't look too closely.

On 12/14/2017 5:28 PM, Ali Ebrahimi wrote:
> Hi Brain,
> +1;
> Can we say existing switch construct arms results void value (by 
> break) but we want to enhance that to results non-void values (using 
> break by value).
> In other word, the result type of existing switch construct is void 
> but now based on target type of switch construct can be non-void and 
> it is required to break by value.
> Note:I'm deliberately avoiding 'return' term and use result term 
> instead here.
> On Fri, Dec 15, 2017 at 12:52 AM, Brian Goetz <brian.goetz at oracle.com 
> <mailto:brian.goetz at oracle.com>> wrote:
>     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."
> -- 
> Best Regards,
> Ali Ebrahimi

More information about the amber-spec-observers mailing list