On Mar 13, 2018, at 1:02 PM, Kevin Bourrillion <kev...@google.com> wrote:
> 
> The more I have thought about it, the more I believe that 95% of the entire 
> value of expression switch is that it isn't procedural switch, and is easier 
> to reason about than procedural switch because of all things it can't do: 
> can't miss cases
> can't return
> can't break/continue a containing construct
> can't fall through
> (for constants or other disjoint patterns) can't depend on the order of cases.
> As far as I can tell, its limitations are exactly what make it useful. (It 
> isn't really a large savings of code bulk, and it's not that we really want 
> to see switches appearing in expression contexts in general besides the key 
> ones `var = ??` and `return ??`.)
> 
> I also believe that all these limitations work to support the notion that an 
> expression switch is functional in nature. It is a function defined "in 
> parts" (and immediately executed). As such, I believe we should discourage 
> using expression switch in side-effecting ways. More to the point, I suggest 
> that side-effecting use cases should not be seen as especially motivating for 
> our design decisions (see e.g. my message 30 minutes ago in "break seen as a 
> C archaism").


These are all real issues but they don't all cut so uniformly in
the direction you are seeking to uphold.  I would refactor your
list as:

A. must complete with a value or throw; cannot complete with control flow 
(covers return/break/continue)
B. is exhaustive (can't miss cases, can't fall through)
C. when patterns are disjoint, case order is insignificant
D. acts like a lambda body (is functional, no external side effects)

For expression switches, A is truly a unique requirement.  In Java expressions
cannot complete with a branch; they must complete with a value or throw.
I don't think any of us want to add a new kind of expression which suddenly
can branch to a visible label or return, without the help of an enclosing branch
statement.  As for statements, they can branch, within limits (lambda bodies
and method bodies).

B is a requirement that is necessary for expression switches also, but it is
also a desirable property of *some* statement switches.  So there's some
design work to do motivated by e-switches that will benefit s-switches.
I'm thinking in particular of some simple way to certify that a switch
(either kind) is intended to be exhaustive, asking the static and runtime
systems to give suitable diagnostics if that ever fails.

(Or is "fallthrough" the phenomenon where several case labels converge
to one statement?  I am doubtful that is what you mean because it seems
expression switches are very likely to need to reply to several target
values with one expression, just as with statement switches.)

C is a tautology, so I'm not sure what it tells us.  Are you saying that
order-invariance is an important property for expressions but not
statements?  When we get to overlapping case labels (patterns)
they will be equally welcome in s-switches and e-switches.

D is an extension of A, upgrading the branch-free property to the
absence of all side effects, making s-switches like lambdas.  I do
*not* think this is a realistic goal, not even for a highly disciplined
shop like Google.  Why?  Because in Java expressions have lots
of side effects.  Consider:

Object x = i < len ? a[i++] : null;
Object x = it.hasNext() ? it.next() : null;

Those expressions are two-branch conditionals (disguised
if-statements) with side effects.  They are not "functional" in any
robust sense (the iterator is a shallow container for non-functional
state just like a and i++).

As soon as you have more than two branches to your conditional,
you want a switch expression, and it may very well operate on
ambient state, just like many other Java expressions:

Object x = switch (len - i) {
  case 0 -> null;
  case 1 -> a[i++];
  default:
  case 2 -> (a[i++] << 8) + a[i++];
};

The way I see it, D is undesirable, A is necessary to the physics
of expressions but doesn't tell us anything about the nature of
e-switch, and B and C apply to both kinds of switches.  So
there's nothing here that teaches us to treat e-switch as
something with its own special mission defined by its limitations.

Instead, I very much believe in Brian's design heuristic of
running a refactoring exercise over switch use cases, to make
sure that there is (when possible) an easy transition between
s-switch and e-switch.  The effect of this heuristic is to keep
both switches aligned in their capabilities (where physics allow)
lowering the learning burden, and making it easy for programmers
to convert between the forms as needs and tastes require.

— John

Reply via email to