On May 8, 2018, at 12:31 PM, Brian Goetz <brian.go...@oracle.com> wrote: > > If continue is the right notion, our choice is essentially between: > - drive towards continue in switch working like it does in everything else, > and deal with ambiguities (switch in loop) as they come up; > - drive towards something that looks sufficiently different from "naked" > continue (continue with label, "continue switch") in switches, and accept > there will be a permanent seam (in switch you do it this way, in loops, this > other way.)
(Cards on table: I'm on Team `continue switch;` and $0.02 follows.) The permanent seam doesn't bother me very much, for the following explainable reason: Switch is not a proper loop, although it is a continuable statement. If you want to continue a continuable statement that isn't a loop, you have to be specific. (And you should be specific anyway if your code is deeply nested.) Classic Java loops while/for/do-while are (usually) not statically bounded. Depending on their logic they can run an arbitrary number of times. Now, by saying "continue" in a switch, we are ascribing it a loopish nature. And this is true, because a switch is (in our new theory) a _decision chain_, that is a finite, sequence of statically defined predicates which are tested in order until one matches. The number of predicates is arbitrary (like a loop) but they are listed statically (unlike a loop). In both loops and decision chains there is a well-defined and very useful control flow operation, which is "continue to the next step". The next step of the loop is to run the iteration and execute the loop body again. The next step of the decision chain is to abandon the current predicate and test the next one. The "continue" concept fits the bill for both. But compatibility says a bare "continue" must go to a proper loop, not a decision chain. Thus, bare continue always reaches the enclosing proper loop, but "continue switch" or "continue L" (L labeling the switch) reaches the enclosing switch, because although switches are not proper loops, they are loopish, they are continuable even if they cannot run forever like a loop. Explained this way, the "seam" Brian is referring to is an artifact, not of history, but of the distinction between proper loops and other continuable ("loopish") constructs. Personally I'd be fine with this seam as a permanent thing. The seam can be reduced also, and I think that is what Brian is aiming at. Bare "continue" in an expression switch will always be unambiguous, since it *cannot* reach an enclosing loop (no branches out from an expression.) Sailing yet more closely to the wind: Bare "continue" in a switch *not* nested in a loop is also unambiguous, since there's no proper loop to reach. Brian, are you thinking that bare continue, inside switch, *inside loop*, is an *ambiguity error*? That would be worth warning about about: Today's correct code would become an error tomorrow: for (;;) { switch (x) { case 0: continue; // Error/warning: ambiguous unlabeled continue } } The message could say "unlabeled continue is ambiguous when nested in both switch and proper loop, repair by saying either 'continue switch' or 'continue for', or use a label." In that case, the warnings could be sent even after feature adoption. Eventually when the warnings turn to errors, no code changes semantics, but some code breaks. Or make it be a warning forever. OK, now for some "decision chain theory". Besides switches, decision chains have two other forms which are worth contemplating, as elucidating the essential structure in another surface form, and also as a possible refactoring target. switch (x) { case P1 -> S1; // suppressing legacy fallthrough for simplicity case P2 -> S2; … } if (x matches P1) S1; else if (x matches P2) S2; else … for (Function<XType, Optional<YType>> casef : List.of( x -> x matches P1 ? Optional.of(S1) : Optional.none(), x -> x matches P2 ? Optional.of(S2) : Optional.none() )) { var y = casef.apply(x); if (!y.isPresent()) continue; result = y.get(); break; } The third form seems exotic but it is a coding pattern some of us have surely used for decision chains, test lists, and on similar occasions. (I have!) The continue keyword is native in the third form, and corresponds exactly to the proposed continue [switch] form for the first form. OK, now I'm going to push farther, by your leave, with a thought experiment exploring and extending the correspondence between the first two forms of decision chain, switch and if/else. The second form is of course a far more common refactoring of decision chains; in fact one of the motivations of pattern-switch is to refactor many existing if/else decision chains into easier-to-read switches. Here's an example: if (x matches Plus(0.0, var a)) { res = a; } else if (x matches Plus(var a, 0.0)) { res = a; } else { res = x; } This simplifies to a switch-based decision chain: switch (x) { case Plus(0.0, var a) -> res = a; case Plus(var a, 0.0) -> res = a; default -> res = x; } Here's the odd part: The close duality between if and switch forms suggests that we should also consider "continue if", as a form which means "find the innermost enclosing 'if' with a continuation point and branch to it". What on earth is a continuation point of an 'if'? That's easy, it's spelled "else". In other words, if/else (not just if w/o else) is loopish too. (And "if" without an "else" gets passed by from "continue" since there is no continuation point.) Here's the same example, but enhanced with guards (isNan): switch (x) { case Plus(0.0, var a): if (a.isNan()) continue switch; res = a; break; case Plus(var a, 0.0): if (a.isNan()) continue switch; res = a; break; default: res = x; } What's the equivalent if/else decision chain, with the same guards? if (x matches Plus(0.0, var a) && !a.isNan()) { res = a; } else if (x matches Plus(var a, 0.0) && !a.isNan()) { res = a; } else { res = x; } But this one pushes the guards to one side, making the basic structure easier to read (in some cases, not all!): if (x matches Plus(0.0, var a)) { if (a.isNan()) continue if; res = a; } else if (x matches Plus(var a, 0.0)) { if (a.isNan()) continue if; res = a; } else { res = x; } How often have you added a complicated "&& !foo" rider expression to an otherwise clear "if/else" chain, just to push control forward towards the next "else" which miight handle your marginal "foo" condition? I have, many times. I think "continue if" would have been helpful. (Note that "break if" is slightly useful too, if you just want to execute a localized action for some marginal condition and be done. Today you need to nest your main action equally with the marginal action, putting an if/else inside the if/else. That's not too bad, but we might sometime might prefer the option of making the marginal case be an asymmetrical add-on to the main flow of logic.) — John