break seen as a C archaism

Brian Goetz brian.goetz at oracle.com
Fri Mar 9 22:15:21 UTC 2018


I understand where these people are coming from.  But my experience is, 
with a platform as mature as Java, one must be very, very careful of the 
desire to opportunistically "fix" "mistakes" of the past, it can be a 
siren song that draws you to the rocks.  I am skeptical of arguments 
that "we should kill break (or at least, not make it more important), 
because it's old stuff and we're young and hip", even though I have a 
certain sympathy for this argument.  (Well, I'm old, and was never hip, 
but I'd like to try it someday.)

Fallthrough is certainly one of the biggest punching bags of Java.  
However, the problem with fallthrough is not fallthrough itself, but the 
fact that fallthrough is the default, when 98% of the time you do not 
want fallthrough.  That's a separate problem -- and might admit a 
separate solution.

In switch expressions, 98% of the time, except maybe in the default arm, 
no one will ever have to type break, because you can usually say:

     int x = switch (y) {
         case 1 -> 2;
         case 2 -> 4;
         case 3 -> 8;
         default:
             throw new TooBigException();
     }

See, no break.  But sometimes, you will need it.

There are basically two stable ways we can go here:
  - Renovate switch as best we can to support expressions and patterns.
  - Leave switch in the legacy bin, and make a new construct, say 
"match".  (Note that doing this helps with the fallthrough-by-default, 
but doesn't really help switch expressions at all -- we still have to 
solve the same problem.)

There are costs to both, of course.  (Engineers tend to over-rotate 
towards the second because it seems more fun and modern, but sticking 
with what works, and what Java developers _already understand_, is often 
better.)  Our current strategy is to stick with what works until that 
approach is proven unworkable.

I think trying to "tame" switch is less stable; if we're going to stick 
with switch, we should avoid unnecessary discontinuities between make 
statement and expression switch.

> For others, it elevates the status of break and break is seen as something wrong, an archaism from C.

I think this is really another form of the emotional "its ugly" reaction.

> When i asked what we should do instead, the answer is either:
>    1/ we should not allow block of codes in the expression switch but only expression

This option is not only dislikable (as you suggest), but naive. While 
most of the time, you can say what you want in one expression, there 
will be times where you'll want to do things like the following:

     String y = switch (x) {
         case 1:
             System.out.println("DEBUG: found where the 1 is coming from!");
             break "one";

        case 2:
            if (throwOnTwo)
                throw new TwoException();
            else
                break "two";

        case 3:
             StringMaker sm = new StringMaker();
             sm.setSeed(System.currentTimeMillis());
             break sm.randomString();
     }

While all of these are likely to be infrequent, telling people "just 
refactor your switch to a chain of if-then-else if you want to do that" 
is going to go over like the proverbial lead balloon.

So, no to that one.

>    2/ that we should use the lambda syntax with return, even if the semantics is different from the lambda semantics.

Yes, we considered this, and actually thought this was a clever idea for 
a short while.  (General lesson: beware of clever-seeming ideas.)  But, 
among other problems, reusing "return" in this manner is even more of an 
abuse than reusing "break".  It's a credible choice, but it has its own 
problems too.

> So should we backup and re-use the lambda semantics instead of enhancing break ?

Pesonally, I think if we're going to stick with switch, the current 
proposal -- which generalizes the existing switch semantics -- is 
staying more true to what switch is.  If we find we have to abandon 
switch and do something else, then I think many more options are on the 
table.

BTW, I think most people misunderstand the current proposal because its 
usually explained backwards.  So let me explain it forwards, and I think 
it makes more sense this way.

STEP 1: Extend switch so it can be an expression.  Extend break so it 
can take a value to be yielded from the switch expression.

This means that everything about statement switches and expression 
switches are the same, except that a statement switch can terminate 
nonlocally and an expression switch can't.  But it is a very 
straightforward extension of the control flow of switch to expressions, 
and that's a plus.  What's ugly about it is that you have to say break a 
lot:

     int x = switch (y) {
         case 1: break 2;
         case 2: break 4;
         case 3: break 8;
         default: throw new TooBigException();
    }

Which brings us to step two, which is purely a syntactic optimization 
for the very common case where an expression switch arm has no 
statements other than break:

STEP 2: In a switch expression, allow:

     case label -> e

as shorthand for

     case label: break e

(much as an expression lambda is a shorthand for a statement lambda.)

If you explain it the other way, people think that -> is what makes the 
switch an expression switch, and then the break rule seems weirder.





More information about the amber-spec-experts mailing list