Re: break-with

2019-02-01 Thread Brian Goetz

> It seems we’re all in favor of break-with over unadorned “break”?  

Just for the record, there are possibly-complementary options in this 
direction.  This is not a proposal, as much as putting them on the record.

 - Allow `break-with` as a synonym for `return` in lambdas.  Using `return` was 
an uncomfortable choice (but this may well be locking the barn after the horse 
escapes.)
 - `break-to` as a synonym for labeled break.  
 - `break-from for | while | do` as a synonym for “break from the innermost 
control construct of this kind” (as an alternative to creating a label.)  

Overall I don’t find any of these terribly compelling, but perhaps this may jog 
some actually-good ideas.  




Re: break-with

2019-02-01 Thread Kevin Bourrillion
I like `break-with value();` much better than `break value();` - and would
also agree that that change alone shouldn't require re-previewing the
feature.


On Thu, Jan 17, 2019 at 11:28 AM Brian Goetz  wrote:

> > Being able to call this something like `break-with v` (or some other
> derived keyword) would have made this all a lot simpler. (BTW, we can still
> do this, since expression-switch is still in preview.)
>
> It seems we’re all in favor of break-with over unadorned “break”?
>
> Which feeds into the bigger question about promoting expression switch to
> final in 13.  I don’t think this syntactic change on its own merits
> re-previewing the feature; this is exactly the sort of “feature is
> finished, but we might change the paint color based on feedback” kind of
> thing that the preview mechanism was intended for.
>
> We don’t have to make this decision quite yet, but sometime between now
> and feature-freeze for 13 (June) we have to take one of the following
> actions:
>
>  - File a JEP to make it a permanent feature, possibly with changes
>  - File a JEP to re-preview it, possibly with changes
>  - Withdraw the feature
>
> We can continue to gather feedback on the feature and revisit later.
>
>
>

-- 
Kevin Bourrillion | Java Librarian | Google, Inc. | kev...@google.com


Re: break seen as a C archaism

2018-03-16 Thread Guy Steele

> On Mar 16, 2018, at 5:39 AM, Peter Levart  wrote:
> 
> Expanding on do…

Well, as long as we are fantasizing:

> On 03/16/18 09:50, Peter Levart wrote:
>> And if "while (false)" could be optional, we get:
> 
> Or better yet, make "while (true)" optional even in statement do, so we can 
> finally get away with some more boilerplate:
> 
> for (;;) {
> }
> 
> or
> 
> while (true) {
> }
> 
> and simply do:
> 
> do {
> }
> 
> For e-do, the choice of default "while (true)" is fine, because it aligns 
> with the fact that there has to be a break  somewhere to exit it 
> anyway because it has to yield a result. But there will be some danger that a 
> programmer codes an infinite loop by mistake.

This idea of making “while(true)” be the default has made me realize that Java 
has excellent syntactic space available to support a style of programming that 
was mildly popular in the late 1970s and early 1980s: Dijkstra’s “guarded 
commands” as described in his 1976 book “A Discipline of Programming”.  It’s a 
beautifully concise and symmetric theory of control structure, provided that 
you have committed to a style of programming entirely centered around 
assignment statements (which explains why it has largely fallen by the 
wayside).  Consider the following tale a trip down memory lane to an alternate 
universe:


__

Let a guarded command in Java have this form:

case booleanExpression: statement*

(Dijkstra used the form “booleanExpression -> statement*”.  It’s more Java-like 
to use the keyword `case` and a colon.)
__

First, we define a statement form of do:

do { * }

The semantics are: evaluate the guards of all the guarded commands; if none is 
true, then terminate, else nondeterministically choose some command whose guard 
was true, execute its statements, and then repeat this process.

It can be syntactically distinguished from the existing `do` statement in two 
ways: it contains guarded commands rather than simple statements, and there is 
no `while (expression)` at the end.

Example:  { int j = 0; do { case j < n: a[j] = f(j); ++j; } }
__

Corresponding to that is a statement form of `if`:

if { * }

The semantics are: evaluate the guards of all the guarded commands; if none is 
true, then abort (program error), else nondeterministically choose some command 
whose guard was true and execute its statements.

It can be syntactically distinguished from the existing `if` statement because 
there is a left-brace after the keyword `if`.

Example: if { case x <= 0: a = -b; case x >= 0: a = b; }
__

Now, to make expression forms of these puppies (something Dijkstra never 
envisioned), we need a way to yield values.  For an `if`, we just use `break`:

int a = if { case x <= 0: break -b; case x >= 0: break b; };
__

In the same manner as for `switch`, we can abbreviate `: break` as `->`:

int a = if { case x <= 0 -> -b; case x >= 0 -> b; };

This feels very much like an expression switch, but with choices made by 
boolean expressions rather than a switch value.
__

Now, the expression form of `do` is a bit trickier, because the `do` terminates 
only when no guard is true, so the return value cannot be specified in any of 
the contained statements.  The “obvious” answer is:

do { * } break expression

and at this point everyone else on this mailing list breaks out the torches and 
pitchforks to come after me.  :-)
__



—Guy

P.S. “Did you succeed in using your time machine to get guarded commands into 
Java?”
  “Yes, but in a past that hasn’t happened yet—it’s complicated.”

(Tip of the hat to Norm Feuti.)




Re: break seen as a C archaism

2018-03-16 Thread Brian Goetz




I don't know why others find break so archaic. When I 1st saw this 
proposal, I thought that break was very intuitive choice for e-switch.


I think this is mostly an emotional reaction.  There are plenty of 
things to dislike about switch in Java; I think that for some, the 
prospect of switch getting an overhaul but not "fixing" the things about 
it you hate most, feels like a slap in the face.  (I think there's also 
a bit of wishful thinking, that if we had a new shiny expression, we'd 
forget that it is almost but not quite like this existing construct that 
we can almost but not quite forget about.) It's like when you get an 
upgrade to a software package I use a lot, I see they've changed all the 
cosmetic stuff, but the annoying behaviors are still there, and you 
wonder, "what was the point of that upgrade?"  But, the goal was not to 
fix switch, as much as extend it to do new things, and that means the 
things it already did, it should keep doing them that way.


As a reminder: while switch expressions are great (which is why we 
factored them out of the larger pattern effort), they are the 
"opportunistic" feature here; the real story is pattern matching (whose 
first target will be algebraic data types -- records and sealed classes).



Speaking of long returns...

If return was used for "yielding" a result from e-switch, how is one 
supposed to do a return from a method inside the e-switch:


int m(int x) {
    int y = switch (x) {
        case 1: return 12; // I want to return from m() here!
    }
}


Not allowed.  A switch expression, like a conditional expression (or any 
expression, for that matter), must yield a value or throw.  It can't do 
nonlocal control flow into enclosing contexts, except throwing.



And now for something completely different...

I think that with introduction of e-switch, we might soon see it being 
"abused" for things like:


doSomething(
    par1,
    switch (1) { case 1:
        // compute result...
       break resut;
    },
    par3
);

Are there any plans for such construct "without the boilerplate" ?-)


Yes, switch expressions can be abused to become block expressions; 
switch (0) { default: s; s; break e; } becomes a block expression. 
That's ugly enough that maybe it will discourage people from doing this


We don't have plans to fix it, but we're open to ideas; a more natural 
way to treat blocks of statements like:


    var x = new Foo();
    x.setA(3);
    return x;

as an expression would have removed a lot of the angst over this 
feature.  Maybe it could be written


    with (new Foo()) { setA(3); }

or something.  But, its not the top priority right now.


Among existing reserved words, "do" seems most appropriate:


Yes, do { } could become an expression with "break e".  (And maybe even 
make the while optional.)





Re: break seen as a C archaism

2018-03-16 Thread Remi Forax
Hi Peter, 
I think this have been ruled out but Brian saying that we do not want to add a 
block that ends with an expression in Java. 
And i think we can use parenthesis to avoid to re-interpret what a 

int value = do( 
int x = foo(); 
break x * x; 
); 

If we go in that direction, i think i prefer the comprehension-like syntax 
where you put the expression giving the result first and then the block that 
calculates the value, 
it has the advantage of being an expression, so it fits with the arrow syntax 

switch(value) { 
case 0 -> 0 
case 1 -> x * x with { x = foo(); } 
} 

regards, 
Rémi 

> De: "Peter Levart" <peter.lev...@gmail.com>
> À: "John Rose" <john.r.r...@oracle.com>, "Maurizio Cimadamore"
> <maurizio.cimadam...@oracle.com>
> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Vendredi 16 Mars 2018 10:39:05
> Objet: Re: break seen as a C archaism

> Expanding on do...

> On 03/16/18 09:50, Peter Levart wrote:

>> And if "while (false)" could be optional, we get:

> Or better yet, make "while (true)" optional even in statement do, so we can
> finally get away with some more boilerplate:

> for (;;) {
> }

> or

> while (true) {
> }

> and simply do:

> do {
> }

> For e-do, the choice of default "while (true)" is fine, because it aligns with
> the fact that there has to be a break  somewhere to exit it anyway
> because it has to yield a result. But there will be some danger that a
> programmer codes an infinite loop by mistake.

>> doSomething(
>> par1,
>> do {
>> // compute result...
>> break resut;
>> },
>> par3
>> );

> Expanding on e-do... It could be a building block for e-switch. Remi is
> advocating for expression-only case(s) in e-switch. Combined with e-do, we
> could write:

> int y = switch (x) {
> case 1 -> 2;
> case 2 -> 3;
> case 3 -> do {
> r = ...;
> break r;
> };
> };

> What we loose here is fallthrough. And we still have "break" here too.

> It's unfortunate that we couldn't find a way to have an e-{block} of a kind 
> when
> lambdas have been conceived. That way we could get away with expression-only
> lambdas and expression-only cases in e-switch using the same building block.
> But I guess the statement lambda has its weight with its "return" sticking out
> and reminding us about the scope.

> Regards, Peter


Re: break seen as a C archaism

2018-03-16 Thread Peter Levart

Expanding on do...

On 03/16/18 09:50, Peter Levart wrote:

And if "while (false)" could be optional, we get:


Or better yet, make "while (true)" optional even in statement do, so we 
can finally get away with some more boilerplate:


for (;;) {
}

or

while (true) {
}

and simply do:

do {
}

For e-do, the choice of default "while (true)" is fine, because it 
aligns with the fact that there has to be a break  somewhere to 
exit it anyway because it has to yield a result. But there will be some 
danger that a programmer codes an infinite loop by mistake.





doSomething(
    par1,
    do {
        // compute result...
       break resut;
    },
    par3
);



Expanding on e-do... It could be a building block for e-switch. Remi is 
advocating for expression-only case(s) in e-switch. Combined with e-do, 
we could write:


int y = switch (x) {
    case 1 -> 2;
    case 2 -> 3;
    case 3 -> do {
        r = ...;
        break r;
    };
};

What we loose here is fallthrough. And we still have "break" here too.

It's unfortunate that we couldn't find a way to have an e-{block} of a 
kind when lambdas have been conceived. That way we could get away with 
expression-only lambdas and expression-only cases in e-switch using the 
same building block. But I guess the statement lambda has its weight 
with its "return" sticking out and reminding us about the scope.


Regards, Peter



Re: break seen as a C archaism

2018-03-16 Thread Peter Levart

Hi,

May I jump in as an outsider and someone who's just using the language...

On 03/15/18 22:58, John Rose wrote:
On Mar 15, 2018, at 2:13 PM, Maurizio Cimadamore 
> wrote:


So, from a language design perspective, 'return x' is wrong - but, as 
you point out, we already committed the original sin of having 
'return == local return' for lambdas, so I'm not too convinced that 
we couldn't use the same story again here. E.g. when you say 
'return', what you really mean is 'returning from the innermost 
context'. This could be a method (as usual), or a nested expression 
e.g. a lambda or a switch expression.




We have method bodies and lambda bodies on one hand,
and we have switches and loops on the other.


Yes, and my intuitive distinction between those two kinds of constructs 
is that the first are just "declarations" of code blobs, while the 
second are code blobs that execute in-line with the surrounding code. It 
is therefore very intuitive for me to have two kinds of syntax for 
exiting the constructs - "return" for the first and "break" for the second.


I don't know why others find break so archaic. When I 1st saw this 
proposal, I thought that break was very intuitive choice for e-switch.




We use return to escape from the former, and break to
escape from the latter.

Note that return may or may not take an expression,
while break never does, at present.

So far so good.  Now we stir in expression switches.
Which side of the fence do they belong on?

It seems to me that your position needs to argue
that e-switches belong with methods and lambdas,
because only return can take an expression.
If you can pull this off, then break doesn't need
to take an expression.

Likewise, my position need to argue that giving "break" an
expression is reasonable.  I don't need to argue
that expression switches are similar to legacy
switches.  (But I'm trying to spike the argument
that it's hard to unify e-switches and s-switches,
so let's just fork the language with a new switch-like
feature for expressions.)

But there are two reasons why e-switch doesn't
belong with method body and lambda body,
a shallow but strong one, and a deep one.

Shallow but strong:  e-switches are obviously switches.

Deep:  Lambda bodies and method bodies execute
in their own stack frames.  Any up-level references
must be to final locals (or fields).  Lambda bodies
and methods can execute at most one "return",
which tears down their frame.  Expressions,
including expression switches, execute in the
frame of the containing lambda body or method
and can read *and write* local variables.
Expressions are inherently local to a frame
and can imperatively side effect it.


That's another, more technical way of saying: methods and lambdas are 
declarations of code, switches and loops are in-line constructs that 
execute "immediately" in the surrounding context. Lambdas do "capture" 
surrounding context, but they don't execute in it (they can't modify 
locals, do long returns etc.).


Speaking of long returns...

If return was used for "yielding" a result from e-switch, how is one 
supposed to do a return from a method inside the e-switch:


int m(int x) {
    int y = switch (x) {
        case 1: return 12; // I want to return from m() here!
    }
}



A "return" which in some contexts keeps the
stack frame and jumps somewhere is a weaker
return than today's return.  (Weaker meaning
less can be concluded by observing it in code.)

So I can't group e-switch cases with lambda bodies.
I know some have performed this feat to their own
satisfaction, but it's hard for me, in a way that
seems deeper than just learning curve.

By now we recognize that adding an expression
to "break" is no big deal; it's a new overloading.
I agree that it is open to the accusation that it's not
thrifty, that "return" already does that job.
But it seems to me the shallow and deep points
above answer the accusation.

For me, the cost of making "break" do a new
trick is paid for by the benefit of not inventing
a new switch-like expression (like ?: for if/else),
and not having to weaken "return".

— John


I totally agree. There are some caveats though. What to do in situations 
like this, for example:


int var_or_label = 13;
int y = switch (x) {
    case 1:
        var_or_label: {
            break var_or_label;
    }
        // do we reach here?
};


The standard means that Java took to avoid ambiguities caused by new 
features was to prioritize old behavior (varargs for example). In above 
sample, label would take precedence. It is easy to choose the var:


    break (var_or_label);


And now for something completely different...

I think that with introduction of e-switch, we might soon see it being 
"abused" for things like:


doSomething(
    par1,
    switch (1) { case 1:
        // compute result...
       break resut;
    },
    par3
);

Are there any plans for such construct "without 

Re: break seen as a C archaism

2018-03-15 Thread Guy Steele
Well, actually, Brian, I now realize that I had my tongue in only _one_ of my 
cheeks.  Sleep on it and then see what you think.

> On Mar 15, 2018, at 6:38 PM, Brian Goetz <brian.go...@oracle.com> wrote:
> 
> At this point, the Colonel from Monty Python breaks in, and shuts us down for 
> being too silly
> 
> On 3/15/2018 6:37 PM, Remi Forax wrote:
>> 
>> 
>> De: "John Rose" <john.r.r...@oracle.com> <mailto:john.r.r...@oracle.com>
>> À: "Guy Steele" <guy.ste...@oracle.com> <mailto:guy.ste...@oracle.com>
>> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net> 
>> <mailto:amber-spec-experts@openjdk.java.net>
>> Envoyé: Jeudi 15 Mars 2018 23:06:51
>> Objet: Re: break seen as a C archaism
>> On Mar 15, 2018, at 2:44 PM, Guy Steele <guy.ste...@oracle.com 
>> <mailto:guy.ste...@oracle.com>> wrote:
>> 
>> 
>>  break return x;
>> 
>> Then everybody is happy:
>> (1) Cannot be confused with the old `break` syntax.
>> (2) Clearly exits a `switch` like `break` does.
>> (3) Clearly returns a value like `return` does.
>> (4) Better encourages exclusive use of `->` (because using `->` rather than 
>> `: break return` saves even more characters than using `->` rather than `: 
>> break`).
>> (5) In the year 2364, this can be further generalized to allow `continue 
>> return x;`.
>> (6) Those who want new language features to really jump out will surely be 
>> satisfied.
>> 
>> Not bad.  It also doesn't weaken "plain return" in the
>> way I was worried about.
>> 
>> I would have numbered that last point (-1), though.
>> 
>> — John
>> 
>> i think, we're missing a 'do' just to be sure,
>>   do break return x;
>> 
>> Rémi
>> 
> 



Re: break seen as a C archaism

2018-03-15 Thread Brian Goetz
At this point, the Colonel from Monty Python breaks in, and shuts us 
down for being too silly


On 3/15/2018 6:37 PM, Remi Forax wrote:





*De: *"John Rose" <john.r.r...@oracle.com>
*À: *"Guy Steele" <guy.ste...@oracle.com>
*Cc: *"amber-spec-experts" <amber-spec-experts@openjdk.java.net>
*Envoyé: *Jeudi 15 Mars 2018 23:06:51
*Objet: *Re: break seen as a C archaism

On Mar 15, 2018, at 2:44 PM, Guy Steele <guy.ste...@oracle.com
<mailto:guy.ste...@oracle.com>> wrote:


break return x;

Then everybody is happy:
(1) Cannot be confused with the old `break` syntax.
(2) Clearly exits a `switch` like `break` does.
(3) Clearly returns a value like `return` does.
(4) Better encourages exclusive use of `->` (because using
`->` rather than `: break return` saves even more characters
than using `->` rather than `: break`).
(5) In the year 2364, this can be further generalized to allow
`continue return x;`.
(6) Those who want new language features to really jump out
will surely be satisfied.


Not bad.  It also doesn't weaken "plain return" in the
way I was worried about.

I would have numbered that last point (-1), though.

— John


i think, we're missing a 'do' just to be sure,
  do break return x;

Rémi





Re: break seen as a C archaism

2018-03-15 Thread Remi Forax
> De: "John Rose" <john.r.r...@oracle.com>
> À: "Guy Steele" <guy.ste...@oracle.com>
> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Jeudi 15 Mars 2018 23:06:51
> Objet: Re: break seen as a C archaism

> On Mar 15, 2018, at 2:44 PM, Guy Steele < [ mailto:guy.ste...@oracle.com |
> guy.ste...@oracle.com ] > wrote:

>> break return x;

>> Then everybody is happy:
>> (1) Cannot be confused with the old `break` syntax.
>> (2) Clearly exits a `switch` like `break` does.
>> (3) Clearly returns a value like `return` does.
>> (4) Better encourages exclusive use of `->` (because using `->` rather than 
>> `:
>> break return` saves even more characters than using `->` rather than `:
>> break`).
>> (5) In the year 2364, this can be further generalized to allow `continue 
>> return
>> x;`.
>> (6) Those who want new language features to really jump out will surely be
>> satisfied.

> Not bad. It also doesn't weaken "plain return" in the
> way I was worried about.

> I would have numbered that last point (-1), though.

> — John

i think, we're missing a 'do' just to be sure, 
do break return x; 

Rémi 


Re: break seen as a C archaism

2018-03-15 Thread Guy Steele
Okay, Maurizio, you got me thinking.  As long as we are convinced that we are 
actually going to use an explicit value-returning statement within a switch 
expression quite infrequently in real code, why don’t we get the best of both 
worlds by spelling it this way:

break return x;

Then everybody is happy:
(1) Cannot be confused with the old `break` syntax.
(2) Clearly exits a `switch` like `break` does.
(3) Clearly returns a value like `return` does.
(4) Better encourages exclusive use of `->` (because using `->` rather than `: 
break return` saves even more characters than using `->` rather than `: break`).
(5) In the year 2364, this can be further generalized to allow `continue return 
x;`.
(6) Those who want new language features to really jump out will surely be 
satisfied.

—Guy

> On Mar 15, 2018, at 5:13 PM, Maurizio Cimadamore 
>  wrote:
> 
> So, from a language design perspective, 'return x' is wrong - but, as you 
> point out, we already committed the original sin of having 'return == local 
> return' for lambdas, so I'm not too convinced that we couldn't use the same 
> story again here. E.g. when you say 'return', what you really mean is 
> 'returning from the innermost context'. This could be a method (as usual), or 
> a nested expression e.g. a lambda or a switch expression.
> 
> Kevin has a point in that using return is mildly worrisome when it comes to 
> refactoring; but we had exactly the same problem with lambdas when we were 
> considering migrating code using internal iteration (for loop) to code using 
> external iteration (Stream forEach) - again, there the refactoring could not 
> be 100% smooth - if the body of your loop had some abnormally completing 
> branches, then there was no way to translate that into an external iteration 
> idiom - at least not mechanically (e.g. 'return x' already meant something 
> different inside old-style for loop bodies).
> 
> So, seems to me that we end up with the same bag of pros and cons? E.g. more 
> familiar to the user (return  is something that they know and love), 
> but more smelly from a design point of view (in a way that forecloses using 
> 'return' to mean non-local return, but I wonder - has that ship already 
> sailed?)
> 
> Maurizio



Re: break seen as a C archaism

2018-03-15 Thread John Rose
On Mar 15, 2018, at 2:13 PM, Maurizio Cimadamore 
 wrote:
> 
> So, from a language design perspective, 'return x' is wrong - but, as you 
> point out, we already committed the original sin of having 'return == local 
> return' for lambdas, so I'm not too convinced that we couldn't use the same 
> story again here. E.g. when you say 'return', what you really mean is 
> 'returning from the innermost context'. This could be a method (as usual), or 
> a nested expression e.g. a lambda or a switch expression.
> 
> 
We have method bodies and lambda bodies on one hand,
and we have switches and loops on the other.

We use return to escape from the former, and break to
escape from the latter.

Note that return may or may not take an expression,
while break never does, at present.

So far so good.  Now we stir in expression switches.
Which side of the fence do they belong on?

It seems to me that your position needs to argue
that e-switches belong with methods and lambdas,
because only return can take an expression.
If you can pull this off, then break doesn't need
to take an expression.

Likewise, my position need to argue that giving "break" an
expression is reasonable.  I don't need to argue
that expression switches are similar to legacy
switches.  (But I'm trying to spike the argument
that it's hard to unify e-switches and s-switches,
so let's just fork the language with a new switch-like
feature for expressions.)

But there are two reasons why e-switch doesn't
belong with method body and lambda body,
a shallow but strong one, and a deep one.

Shallow but strong:  e-switches are obviously switches.

Deep:  Lambda bodies and method bodies execute
in their own stack frames.  Any up-level references
must be to final locals (or fields).  Lambda bodies
and methods can execute at most one "return",
which tears down their frame.  Expressions,
including expression switches, execute in the
frame of the containing lambda body or method
and can read *and write* local variables.
Expressions are inherently local to a frame
and can imperatively side effect it.

A "return" which in some contexts keeps the
stack frame and jumps somewhere is a weaker
return than today's return.  (Weaker meaning
less can be concluded by observing it in code.)

So I can't group e-switch cases with lambda bodies.
I know some have performed this feat to their own
satisfaction, but it's hard for me, in a way that
seems deeper than just learning curve.

By now we recognize that adding an expression
to "break" is no big deal; it's a new overloading.
I agree that it is open to the accusation that it's not
thrifty, that "return" already does that job.
But it seems to me the shallow and deep points
above answer the accusation.

For me, the cost of making "break" do a new
trick is paid for by the benefit of not inventing
a new switch-like expression (like ?: for if/else),
and not having to weaken "return".

— John

Re: break seen as a C archaism

2018-03-15 Thread Remi Forax
- Mail original -
> De: "mark" <m...@io7m.com>
> À: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Jeudi 15 Mars 2018 20:06:40
> Objet: Re: break seen as a C archaism

> On 2018-03-15T14:50:45 -0400
> Brian Goetz <brian.go...@oracle.com> wrote:
>>
>> > If you are reconsidering options, reconsider "yield", meaning
>> >"break current context with this value".
>> 
>> Still feeling a little burned by first time we floated this, but willing
>> to try another run up the flagpole
> 
> Silly idea, but... *puts on fireproof suit*:
> 
>  "finally x;"

I believe we can also use any new keywords given that you can not have an 
identifier followed by an identifier in Java.

by example
  pass x;
  quit x;
  end x;
  
> 
> --
> Mark Raynsford | http://www.io7m.com

Rémi


Re: break seen as a C archaism

2018-03-15 Thread Guy Steele

> On Mar 15, 2018, at 3:40 PM, Remi Forax <fo...@univ-mlv.fr> wrote:
> 
> 
> 
> - Mail original -
>> De: "Guy Steele" <guy.ste...@oracle.com>
>> À: "mark" <m...@io7m.com>
>> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
>> Envoyé: Jeudi 15 Mars 2018 20:18:34
>> Objet: Re: break seen as a C archaism
> 
>>> On Mar 15, 2018, at 3:06 PM, Mark Raynsford <m...@io7m.com> wrote:
>>> 
>>> On 2018-03-15T14:50:45 -0400
>>> Brian Goetz <brian.go...@oracle.com> wrote:
>>>> 
>>>>> If you are reconsidering options, reconsider "yield", meaning
>>>>>  "break current context with this value".
>>>> 
>>>> Still feeling a little burned by first time we floated this, but willing
>>>> to try another run up the flagpole
>>> 
>>> Silly idea, but... *puts on fireproof suit*:
>>> 
>>> "finally x;"
>> 
>> Interestingly, the keywords `try` and `catch` and `finally` currently must 
>> each
>> be followed by a block, so there is indeed syntactic space to use each one 
>> with
>> a following expression instead.
>> 
>> Which only suggests that . . . *puts on fireproof suit and then climbs into a
>> concrete bunker and slams the door*:
>> 
>>  “try x;”
>> 
>> would be shorter and no sillier.
>> 
>> —Guy
> 
> It seams too close to the try-with-resources.
> 
> compare
>  try (resource) -> { };  // a try that break/return a lambda
> with
>  try (resource) { }  // a try-with-resources
> 
> Rémi

Indeed.  Mine was not a serious suggestion.  I agree with John Rose’s analysis: 
“break x;” really does seem to be the best point in the design space, 
especially since you can use “->” to hide it 98% of the time.

"But I was thinking of a plan
To dye one's whiskers green,
And always use so large a fan
That they could not be seen.”

—Lewis Carroll




Re: break seen as a C archaism

2018-03-15 Thread Remi Forax


- Mail original -
> De: "Guy Steele" <guy.ste...@oracle.com>
> À: "Brian Goetz" <brian.go...@oracle.com>
> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Jeudi 15 Mars 2018 20:12:56
> Objet: Re: break seen as a C archaism

>> On Mar 15, 2018, at 2:50 PM, Brian Goetz <brian.go...@oracle.com> wrote:
>> 
>> 
>>>> We had rejected this earlier for fairly obvious reasons, but let me
>>>> ask to get a subjective response: would using "return x" be better?
>>> If you are reconsidering options, reconsider "yield", meaning
>>>   "break current context with this value".
>> 
>> Still feeling a little burned by first time we floated this, but willing to 
>> try
>> another run up the flagpole
>> 
>> In Lambda, I used the early "State of the Lambda" drafts as a means to
>> test-drive various syntax options.  SotL 2/e floated "yield" as the
>> get-out-of-lambda card, and I was unprepared for the degree of "you big fat
>> stupid idiot, don't you know what yield means" response I got.  So we beat a
>> hasty retreat from that experiment, temporarily settled on return, and then
>> failed to circle back.  I still regret the choice of return for lambda.
>> 
>> The primary objection to yield was from the async/await crowd that would 
>> want us
>> to save it for that, but I don't see them as mutually exclusive (nor do I 
>> think
>> async/await is all that likely, especially with the great work happening over
>> in Loom).
>> 
>> The loss of using something other than "break" is that now expression and
>> statement switches become more obviously different beasts, which might be OK.
> 
> I have to agree that “yield” has too much of a history in the topics of
> multithreading and coroutining, giving it all the wrong connotations for our
> purpose here.

yes !
and if Loom at some point requires a syntax instead of being a pure API, it 
will be unfortunate.

Rémi


Re: break seen as a C archaism

2018-03-15 Thread Remi Forax


- Mail original -
> De: "Guy Steele" <guy.ste...@oracle.com>
> À: "mark" <m...@io7m.com>
> Cc: "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Jeudi 15 Mars 2018 20:18:34
> Objet: Re: break seen as a C archaism

>> On Mar 15, 2018, at 3:06 PM, Mark Raynsford <m...@io7m.com> wrote:
>> 
>> On 2018-03-15T14:50:45 -0400
>> Brian Goetz <brian.go...@oracle.com> wrote:
>>> 
>>>> If you are reconsidering options, reconsider "yield", meaning
>>>>   "break current context with this value".
>>> 
>>> Still feeling a little burned by first time we floated this, but willing
>>> to try another run up the flagpole
>> 
>> Silly idea, but... *puts on fireproof suit*:
>> 
>>  "finally x;"
> 
> Interestingly, the keywords `try` and `catch` and `finally` currently must 
> each
> be followed by a block, so there is indeed syntactic space to use each one 
> with
> a following expression instead.
> 
> Which only suggests that . . . *puts on fireproof suit and then climbs into a
> concrete bunker and slams the door*:
> 
>   “try x;”
> 
> would be shorter and no sillier.
> 
> —Guy

It seams too close to the try-with-resources.

compare
  try (resource) -> { };  // a try that break/return a lambda
with
  try (resource) { }  // a try-with-resources

Rémi


Re: break seen as a C archaism

2018-03-15 Thread Guy Steele

> On Mar 15, 2018, at 3:06 PM, Mark Raynsford  wrote:
> 
> On 2018-03-15T14:50:45 -0400
> Brian Goetz  wrote:
>> 
>>> If you are reconsidering options, reconsider "yield", meaning
>>>   "break current context with this value".  
>> 
>> Still feeling a little burned by first time we floated this, but willing 
>> to try another run up the flagpole
> 
> Silly idea, but... *puts on fireproof suit*:
> 
>  "finally x;"

Interestingly, the keywords `try` and `catch` and `finally` currently must each 
be followed by a block, so there is indeed syntactic space to use each one with 
a following expression instead. 

Which only suggests that . . . *puts on fireproof suit and then climbs into a 
concrete bunker and slams the door*:

   “try x;”

would be shorter and no sillier.

—Guy



Re: break seen as a C archaism

2018-03-15 Thread John Rose
On Mar 15, 2018, at 11:11 AM, Brian Goetz  wrote:
> We had rejected this earlier for fairly obvious reasons, but let me
> ask to get a subjective response: would using "return x" be better?
> On the one hand, it's not really a return, and it doesn't build on the
> user intuition about the control flow aspects of break, but on the
> other, the return statement is already prepared to take a value, so
> its not adding a "new form" to the existing statement, though it is
> adding a new and different context.  (We abuse it slightly in lambdas,
> but people seem OK with this, probably because they think of lambdas
> as methods anyway.)

Here's my take on how this is going, for what it's worth.

We're going round and round on this because there's isn't a comfortable
spot to land.  But there is a nearly comfortable spot, which is where we
started:  Although break's legacy syntax requires an overload for it to
accept a value, the following two legacy facts pull us toward break:
  - break is to switch as return is to a method body (in branch behavior)
  - return is a branch which can be overloaded with an optional value
Now, switches are becoming more like methods: They can return values.
So the most direct path is to overload break "like return", in some way.
What way?  Well, the straightforward way works, with the usual tricks
to avoid ambiguities in overloaded constructs.  At that point we have
added to the language by increasing symmetry, a win.

Let's beware of making new language features #{ Stand Out }.  It makes
them look silly and puzzling when they mature.  Many objections to
novelties like "break x" are of two potential kinds:  a. "I want the new
thing to be easier to spot", b. "Programmers will never be at ease with
that".  People tend to use a. as a proxy for b., but as we go round
and round I think many of us tend to forget about b., which is the
real point.  Can anyone successfully argue that "break x" liable to b.?
I doubt it.

Our quest for continuity and symmetry with the past is our best
trick for avoiding b. (and c., "Let's fork the language"), even at
the temporary cost of a.

Neal Gafter says it very well:
> It is better to design the feature so that it fits well with the existing
> features of the language, even if it might at first seem jarring that
> things have changed. Users will quickly get over the newness of
> the changes and learn to understand the language as a whole as
> it is (after the change).

The adding of -> to the mix is an inspired move (thanks, Brian)
because it repurposes another existing part of the language,
and adds it as sugar for "break x", keeping continuity and
improving clarity at the same time.

My $0.02.

— John

P.S. FTR here's the reference to Neal's blog:
http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html

P.P.S. The above reasoning might lead to other places too:  If we were to
make loops return values, then "break" would do the same for them.
Also "continue" could be overloaded to deliver a partial result to the
loop control.  That's sci fi at the present moment, but I'm showing it as
an example how the logic works.

Re: break seen as a C archaism

2018-03-15 Thread Guy Steele

> On Mar 15, 2018, at 2:50 PM, Brian Goetz  wrote:
> 
> 
>>> We had rejected this earlier for fairly obvious reasons, but let me
>>> ask to get a subjective response: would using "return x" be better?
>> If you are reconsidering options, reconsider "yield", meaning
>>   "break current context with this value".
> 
> Still feeling a little burned by first time we floated this, but willing to 
> try another run up the flagpole
> 
> In Lambda, I used the early "State of the Lambda" drafts as a means to 
> test-drive various syntax options.  SotL 2/e floated "yield" as the 
> get-out-of-lambda card, and I was unprepared for the degree of "you big fat 
> stupid idiot, don't you know what yield means" response I got.  So we beat a 
> hasty retreat from that experiment, temporarily settled on return, and then 
> failed to circle back.  I still regret the choice of return for lambda.
> 
> The primary objection to yield was from the async/await crowd that would want 
> us to save it for that, but I don't see them as mutually exclusive (nor do I 
> think async/await is all that likely, especially with the great work 
> happening over in Loom).
> 
> The loss of using something other than "break" is that now expression and 
> statement switches become more obviously different beasts, which might be OK.

I have to agree that “yield” has too much of a history in the topics of 
multithreading and coroutining, giving it all the wrong connotations for our 
purpose here.



Re: break seen as a C archaism

2018-03-15 Thread Mark Raynsford
On 2018-03-15T14:50:45 -0400
Brian Goetz  wrote:
>
> > If you are reconsidering options, reconsider "yield", meaning
> >"break current context with this value".  
> 
> Still feeling a little burned by first time we floated this, but willing 
> to try another run up the flagpole

Silly idea, but... *puts on fireproof suit*:

  "finally x;"

-- 
Mark Raynsford | http://www.io7m.com



Re: break seen as a C archaism

2018-03-15 Thread Doug Lea
On 03/15/2018 02:11 PM, Brian Goetz wrote:
>> - That we are overloading an existing control construct, "break", 
>> to mean something just different enough to be uncomfortable;
>> 
>> 
>> To some degree yes, since `break ` already means
>> something.
> 
> We had rejected this earlier for fairly obvious reasons, but let me
> ask to get a subjective response: would using "return x" be better?

If you are reconsidering options, reconsider "yield", meaning
  "break current context with this value".

Which is what we are allowing break with val to mean.

Which argues for allowing either (break-val or yield-val) in lambdas as
well because...

> (We abuse it slightly in lambdas, but people seem OK with this,
> probably because they think of lambdas as methods anyway.)

-Doug







Re: break seen as a C archaism

2018-03-14 Thread Louis Wasserman
Just to make sure we have some numbers when talking about fallthrough:

   - Among all switches, we calculate that 2.4% of switches in the Google
   codebase have some nontrivial fallthrough.  (This is possibly an
   overestimate due to overly conservative control flow analysis, but not an
   underestimate.)
   - Determining whether switches with nontrivial fallthrough are
   convertible to expression switches is a little difficult in terms of
   control flow analysis.  As the best proxy with the dataset I already
   scraped,  I defined "convertible to expression switch" as "switches in
   which all cases *that provably exit* either return, or assign to the same
   variable", and among those, 1.2% of switches have nontrivial fallthrough.


On Wed, Mar 14, 2018 at 11:32 AM Brian Goetz  wrote:

>
>
>  - That we are overloading an existing control construct, "break", to mean
>> something just different enough to be uncomfortable;
>>
>
> To some degree yes, since `break ` already means something.
>
>
> Digging deeper: If we spelled "break " differently (yield, emit,
> defuse), would it be significantly different?  I think reusing "return" is
> worse than reusing "break", but there are other choices.  (Though
> introducing a new keyword has its own user-model challenges.)
>
>  Part of it is that I know how to make sense of (a) current switch and (b)
> a simple well-behaved nice expression switch that only uses `->`, but
> knowing that I may have to deal with (c) code that is some mixture between
> the two feels like additional level of complexity to me. Even if from an
> implementation standpoint it's not.
>
>
> I like to think that this is pedagogical, stemming from thinking of switch
> expressions and switch statements as unrelated things.  If we view
> expression switches as a generalization of existing switch, I think that
> the dichotomy between A/B can go away.  But only if there is a clear enough
> explanation that everyone will eventually receive.
>
> C is still an issue, and I do get the discomfort of mixing both -> and :
> cases, and I agree that good style will minimize mixing.  Outlawing mixing
> entirely isn't a great answer, though; its too common to use -> for all the
> cases except default, which often needs statements to do its thing.
>


Re: break seen as a C archaism

2018-03-14 Thread Brian Goetz




 - That we are overloading an existing control construct, "break",
to mean something just different enough to be uncomfortable;


To some degree yes, since `break ` already means something.


Digging deeper: If we spelled "break " differently (yield, emit, 
defuse), would it be significantly different?  I think reusing "return" 
is worse than reusing "break", but there are other choices.  (Though 
introducing a new keyword has its own user-model challenges.)


 Part of it is that I know how to make sense of (a) current switch and 
(b) a simple well-behaved nice expression switch that only uses `->`, 
but knowing that I may have to deal with (c) code that is some mixture 
between the two feels like additional level of complexity to me. Even 
if from an implementation standpoint it's not.


I like to think that this is pedagogical, stemming from thinking of 
switch expressions and switch statements as unrelated things.  If we 
view expression switches as a generalization of existing switch, I think 
that the dichotomy between A/B can go away.  But only if there is a 
clear enough explanation that everyone will eventually receive.


C is still an issue, and I do get the discomfort of mixing both -> and : 
cases, and I agree that good style will minimize mixing.  Outlawing 
mixing entirely isn't a great answer, though; its too common to use -> 
for all the cases except default, which often needs statements to do its 
thing.


Re: break seen as a C archaism

2018-03-14 Thread John Rose
On Mar 14, 2018, at 11:04 AM, Kevin Bourrillion  wrote:
> 
> Part of it is the ability to embed a number of statements inside an 
> expression (please, at least require curly braces, but still).

Well, we do, look again!  The switch as a whole requires the curlies you want.

I'm not being completely flip here.  If we feel a need for for more curlies 
inside
the existing curlies, I think we may be at risk for this syndrome:

Making new language features #{ Stand Out }
http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html 


— John

Re: break seen as a C archaism

2018-03-14 Thread forax
> De: "Brian Goetz" <brian.go...@oracle.com>
> À: "Kevin Bourrillion" <kev...@google.com>
> Cc: "Louis Wasserman" <lowas...@google.com>, "Remi Forax" <fo...@univ-mlv.fr>,
> "amber-spec-experts" <amber-spec-experts@openjdk.java.net>
> Envoyé: Mercredi 14 Mars 2018 02:38:21
> Objet: Re: break seen as a C archaism

> There are three arguments why the N case is significantly different from the 2
> case.

> There are a number of idioms that require statements in addition to an
> expression. Debugging printfs, objects that take statements to initialize
> (construct/set/set/break), incrementing counters, cases that require side
> conditions (if today is tuesday do one thing, otherwise another), etc. Each
> individually is rare-ish, but not all that rare.

> The "static applicability" argument is that the larger the number of cases, 
> the
> more likely one of them will fall into one these buckets, and then the whole
> thing has to fall back to statements. This makes expression switches less
> useful, and falling off this cliff is likely to irritate users every time it
> happens.

As Kevin said in the other thread, instead of refactoring the whole switch, you 
can also add a helper method, after all, if all cases but one are expressions, 
it seems to be a good hint that the code of that case should be extracted to a 
new method. 

They are few switchs in the code (apart if you do parsing, see all the John's 
examples or ASM codes), usually in a program you can count them, so for usual 
code, the more cases you use, the less it's likely to appear. 
And i conjecture that it exists a n such after n cases all switch that exists 
are all in the generated codes. 

> The “dynamic applicability” argument is that, if you want to change an 
> existing
> switch (say, to add a debugging printf in one path), you have to refactor the
> whole thing. Which will be met, by users, with “YGBFKM.”

You mean if the case expression is enough complex that you want to print a part 
of it (otherwise you can printf the result of the switch), you have exactly the 
same issue with any complex expressions in Java, 
again, make the code simpler, create an helper method. 

> The “cliff height” argument says that falling off the cliff on a two-way
> conditional and having to refactor to if-else is far less painful than falling
> off the cliff on an N-way switch. Its a more painful refactor.

The "cliff height" argument was used by Josh Bosch when choosing if 'this' in a 
lambda should mean the lambda itself or the enclosing instance. 
So yes, sometimes, we will have to do a refactoring, IDEs will help, but the 
cliff height is only an argument if you have to jump over it often. 

> So for all these reasons, not being able to occasionally include some 
> statements
> means many more switches that can’t use the feature (which is safer, clearer,
> and more compact), and also more often that the user will have to gratuitously
> refactor perfectly good code as they make small changes.

The 90% argument of Kevin is in my opinion stronger than these arguments. 

Rémi 

>> On Mar 13, 2018, at 8:59 PM, Kevin Bourrillion < [ mailto:kev...@google.com |
>> kev...@google.com ] > wrote:

>>>> - Do nothing; only allow single expressions. Non-starter.

>>> We're just saying the feature seems to be at least 90% as applicable 
>>> without it.
>>> Roughly. Why is it a non-starter for the other 10% to stick with the switch
>>> they've always had? I'm sure there are good answers to that, I'm not 
>>> doubting
>>> there are, but I think we should explore them instead of just declaring
>>> something a non-starter by fiat.

>> Also, if it is true that this is a "non-starter", I would assume it is also a
>> non-starter to only allow single expressions in the conditional operator 
>> `?:`.
>> If not, what is the fundamental difference?


Re: break seen as a C archaism

2018-03-13 Thread Brian Goetz
I get what you’re looking for, I really do.  Existing switch has a lot of 
warts, some of which could be avoided with a new expression switch construct.  
Avoiding warts seems like a good idea, and fall through is _even wartier_ with 
expression switches than statement switches.  (Not sure how you quantify 
wartiness.  Frogs per KLoC?)  

But, the problem is that if we make an expression switch construct, now we have 
two switch constructs.  They’re similar, and you will frequently want to 
refactor between them, but they’re subtly different.  And let’s not assume that 
the new construct will not have warts, so now that’s two different sets of 
warts the user has to keep in their head.  Users who arrive at Java will ask, 
“why are there two subtly different ways to do basically the same thing?”  I 
don’t think that’s necessarily doing anyone a favor.  If the warts were fatal, 
sure, that’s what we’d do, but they’re not.  Mostly, it’s annoying that we’re 
stuck with them for another 20 years. 

While of course I may be too close to it, I think there’s a very nice balance 
in the current proposal.  In addition to control flow working exactly the same 
across the two forms, which I think does reduce the complexity of the language, 
the shorthand for “case X -> e” protects users from the most warty aspects 
almost all the time: 
 - the at-least-at-first weirdness of “break value”;
 - the need to say break at all;
 - the risk of accidental fall through.

But if you need either explicit break or fall-through, they’re there, and they 
work just like the break you’ve always known, for better or worse.  Just 
“break” the glass.  

You’re saying “it wouldn’t be that bad if switch expressions had no fall 
through and only could take single expressions on the RHS, no statements.”  
Well, if you restrict yourself to the -> syntactic form, you get exactly that!  
So the only difference is that the escape hatch exists, if you’re willing to 
use it.  But if you’re not willing to use it, they you get exactly what you 
asked for. 


> On Mar 13, 2018, at 8:43 PM, Kevin Bourrillion  wrote:
> 
> Sorry for 5,000 inline replies.
> 
> 
> On Tue, Mar 13, 2018 at 1:18 PM Brian Goetz  > wrote:
> 
> Thanks for the detailed analysis.  I'm glad to see that a larger percentage 
> could be converted to expression switch given sufficient effort;
> 
> Just to be clear: the sample was only from the 19% already identified as 
> convertible, so in this discussion that number is only going down. However, 
> the important point is that we should separately investigate the 81% to see 
> whether a few simple heuristics make them recognizable as e-switches too.
> 
>  
> I'm also not surprised to see that a lot had accidental reasons that kept 
> them off the happy path.  And, your analysis comports with expectations in 
> another way: that some required more significant intervention than others to 
> lever them into compliance.  For example, the ones with side-effecting 
> activities or logging, while good candidates for "strongly dissuade", happen 
> in the real world, and not all codebases have the level of discipline or 
> willingness to refactor that yours does.  So I'm not sure I'm ready to toss 
> either of the size-7 sub-buckets aside so quick; not everyone is as sanguine 
> as "well, go refactor then" as you are.
> 
> Okay, I want to clarify a few things about this style of research, which is 
> how we have been evaluating API decisions for Guava and our other libraries 
> for a long time, but which I skipped really explaining.
> 
> First, we are using "existing code could refactor to" as a proxy for "what 
> might they probably write anew today if they could". It's a conceit, but a 
> useful one since the corpus of existing code has the benefit of being visible 
> and analyzable. :-) So, arguments that we make don't necessarily rest on 
> whether actual users will actually refactor. (Also, I mean, if they aren't 
> willing to refactor at all, then they wouldn't be changing to expression 
> switch at all so they'd be irrelevant to us anyway, but this is not the real 
> point.)
> 
> Second, we believe that the most useful (not only) way to judge the value of 
> a feature lies in comparing the best code users can write without that 
> feature to the best code they can write with it. Then we look at two factors: 
> (a) how much better did the feature make this code, and (b) how commonly do 
> we think this case comes up in real life. Roughly speaking we multiply (a) 
> and (b) together (for which I have with questionable taste attempted to coin 
> the phrase "utility times ubiquity") and that gives about how much we care 
> about making the change. This type of analysis has driven most decisions 
> about the shape of Guava's API.
> 
> It's not the only kind of argument that can be made. One can also add "it 
> might not make a large amount of 'great' code 'greater', 

Re: break seen as a C archaism

2018-03-13 Thread Brian Goetz
There are three arguments why the N case is significantly different from the 2 
case.  

There are a number of idioms that require statements in addition to an 
expression.  Debugging printfs, objects that take statements to initialize 
(construct/set/set/break), incrementing counters, cases that require side 
conditions (if today is tuesday do one thing, otherwise another), etc.  Each 
individually is rare-ish, but not all that rare.  

The "static applicability" argument is that the larger the number of cases, the 
more likely one of them will fall into one these buckets, and then the  whole 
thing has to fall back to statements.  This makes expression switches less 
useful, and falling off this cliff is likely to irritate users every time it 
happens.  

The “dynamic applicability” argument is that, if you want to change an existing 
switch (say, to add a debugging printf in one path), you have to refactor the 
whole thing.  Which will be met, by users, with “YGBFKM.”  

The “cliff height” argument says that falling off the cliff on a two-way 
conditional and having to refactor to if-else is far less painful than falling 
off the cliff on an N-way switch.  Its a more painful refactor.  

So for all these reasons, not being able to occasionally include some 
statements means many more switches that can’t use the feature (which is safer, 
clearer, and more compact), and also more often that the user will have to 
gratuitously refactor perfectly good code as they make small changes.


> On Mar 13, 2018, at 8:59 PM, Kevin Bourrillion  wrote:
> 
>  - Do nothing; only allow single expressions.  Non-starter.
> 
> We're just saying the feature seems to be at least 90% as applicable without 
> it. Roughly. Why is it a non-starter for the other 10% to stick with the 
> switch they've always had? I'm sure there are good answers to that, I'm not 
> doubting there are, but I think we should explore them instead of just 
> declaring something a non-starter by fiat.
> 
> Also, if it is true that this is a "non-starter", I would assume it is also a 
> non-starter to only allow single expressions in the conditional operator 
> `?:`. If not, what is the fundamental difference?



Re: break seen as a C archaism

2018-03-13 Thread Kevin Bourrillion
On Tue, Mar 13, 2018 at 5:43 PM, Kevin Bourrillion 
wrote:

But setting aside subjective reactions, are there better alternatives?
>> Let's review what has been considered already, and why they've been passed
>> over:
>>
>>  - Do nothing; only allow single expressions.  Non-starter.
>>
>
> We're just saying the feature seems to be at least 90% as applicable
> without it. Roughly. Why is it a non-starter for the other 10% to stick
> with the switch they've always had? I'm sure there are good answers to
> that, I'm not doubting there are, but I think we should explore them
> instead of just declaring something a non-starter by fiat.
>

Also, if it is true that this is a "non-starter", I would assume it is also
a non-starter to only allow single expressions in the conditional operator
`?:`. If not, what is the fundamental difference?

We normally don't get to embed statements inside expressions, except in the
case of anonymous classes and lambdas, where we (a) they must be set off
with curly braces, and (b) they are only embedded physically, and don't
immediately execute. If we do this for switch, we should at least stick
with (a), but (b) is a thing with no precedent. It seems reasonable that we
should require some very solid motivation before breaking that precedent.


-- 
Kevin Bourrillion | Java Librarian | Google, Inc. | kev...@google.com


Re: break seen as a C archaism

2018-03-13 Thread Kevin Bourrillion
Sorry for 5,000 inline replies.


On Tue, Mar 13, 2018 at 1:18 PM Brian Goetz  wrote:

Thanks for the detailed analysis.  I'm glad to see that a larger percentage
> could be converted to expression switch given sufficient effort;
>

Just to be clear: the sample was only from the 19% already identified as
convertible, so in *this* discussion that number is only going down.
However, the important point is that we should separately investigate the
81% to see whether a few simple heuristics make them recognizable as
e-switches too.



> I'm also not surprised to see that a lot had accidental reasons that kept
> them off the happy path.  And, your analysis comports with expectations in
> another way: that some required more significant intervention than others
> to lever them into compliance.  For example, the ones with side-effecting
> activities or logging, while good candidates for "strongly dissuade",
> happen in the real world, and not all codebases have the level of
> discipline or willingness to refactor that yours does.  So I'm not sure I'm
> ready to toss either of the size-7 sub-buckets aside so quick; not everyone
> is as sanguine as "well, go refactor then" as you are.
>

Okay, I want to clarify a few things about this style of research, which is
how we have been evaluating API decisions for Guava and our other libraries
for a long time, but which I skipped really explaining.

First, we are using "existing code could refactor to" as a proxy for "what
might they probably write anew today if they could". It's a conceit, but a
useful one since the corpus of existing code has the benefit of being
visible and analyzable. :-) So, arguments that we make don't necessarily
rest on whether actual users *will actually refactor*. (Also, I mean, if
they aren't willing to refactor *at all*, then they wouldn't be changing to
expression switch at all so they'd be irrelevant to us anyway, but this is
not the real point.)

Second, we believe that the *most* useful (not *only*) way to judge the
value of a feature lies in comparing the *best code* users can write
without that feature to the *best code* they can write with it. Then we
look at two factors: (a) how much better did the feature make this code,
and (b) how commonly do we think this case comes up in real life. Roughly
speaking we multiply (a) and (b) together (for which I have with
questionable taste attempted to coin the phrase "utility times ubiquity")
and that gives about how much we care about making the change. This type of
analysis has driven most decisions about the shape of Guava's API.

It's not the only kind of argument that can be made. One can also add "it
might not make a large amount of 'great' code 'greater', but people who
write mediocre code will be more likely to write decent code!" I just think
that we should put less stock in such arguments than the other kind,
because this sounds like a problem better addressed with static analysis
tools, education, evangelism, and whatnot.



> Which is to say, adjusting the data brings the simple bucket from 86%
> (which seemed low) to 93-95%, which is "most of the time" but not "all of
> the time".  So most of the time, the code will not need whatever escape
> mechanism (break or block), but it will often enough that having some
> escape hatch is needed.
>

We think the ability to stick with procedural switch is that escape hatch
already.


You didn't mention fallthrough, but because that's everyone's favorite
> punching bag here, I'll add: most of the time, fallthrough is not needed in
> expression switches, but having reviewed a number of low-level switches in
> the JDK, it is sometimes desirable even for switches that can be converted
> to expressions.  One of the motivations for refining break in this way is
> so that when fallthrough is needed, the existing idiom just works as
> everyone understands it to.
>

My assumption is that that code can just keep doing what it's already
doing. My claim is that there is only value to changing to expression
switch if we are getting the benefit of how much more simple and
constrained it is.

imho, early signs suggest that the grossness of `break x` is not *nearly*
> justified by the actual observed positive value of supporting
> multi-statement cases in expression switch. Are we open to killing that, or
> would we be if I produced more and clearer evidence?
>
>
> That's one valid interpretation of the data, but not the only.  Whether
> making break behave more like return (takes a value in non-void context,
> doesn't take one in void context) is gross or natural is subjective.
> Here's my subjective interpretation: "great, most of the time, you don't
> have to use the escape hatch, but when you do, it has control flow just
> like the break we know, extended to take a value in non-void contexts, so
> will be fairly familiar."
>

I think that there are features that make sense on their own, and there are
features that *totally make lots 

Re: break seen as a C archaism

2018-03-13 Thread Brian Goetz
Thanks for the detailed analysis.  I'm glad to see that a larger 
percentage could be converted to expression switch given sufficient 
effort; I'm also not surprised to see that a lot had accidental reasons 
that kept them off the happy path.  And, your analysis comports with 
expectations in another way: that some required more significant 
intervention than others to lever them into compliance.  For example, 
the ones with side-effecting activities or logging, while good 
candidates for "strongly dissuade", happen in the real world, and not 
all codebases have the level of discipline or willingness to refactor 
that yours does.  So I'm not sure I'm ready to toss either of the size-7 
sub-buckets aside so quick; not everyone is as sanguine as "well, go 
refactor then" as you are.


Which is to say, adjusting the data brings the simple bucket from 86% 
(which seemed low) to 93-95%, which is "most of the time" but not "all 
of the time".  So most of the time, the code will not need whatever 
escape mechanism (break or block), but it will often enough that having 
some escape hatch is needed.


You didn't mention fallthrough, but because that's everyone's favorite 
punching bag here, I'll add: most of the time, fallthrough is not needed 
in expression switches, but having reviewed a number of low-level 
switches in the JDK, it is sometimes desirable even for switches that 
can be converted to expressions.  One of the motivations for refining 
break in this way is so that when fallthrough is needed, the existing 
idiom just works as everyone understands it to.


imho, early signs suggest that the grossness of `break x` is not 
/nearly/ justified by the actual observed positive value of supporting 
multi-statement cases in expression switch. Are we open to killing 
that, or would we be if I produced more and clearer evidence?


That's one valid interpretation of the data, but not the only. Whether 
making break behave more like return (takes a value in non-void context, 
doesn't take one in void context) is gross or natural is subjective.  
Here's my subjective interpretation: "great, most of the time, you don't 
have to use the escape hatch, but when you do, it has control flow just 
like the break we know, extended to take a value in non-void contexts, 
so will be fairly familiar."


But setting aside subjective reactions, are there better alternatives?  
Let's review what has been considered already, and why they've been 
passed over:


 - Do nothing; only allow single expressions.  Non-starter.
 - Traditional "block expressions"; { S; S; e }.  Terrible fit for 
Java, so no.
 - Some other form of block expression.  Seems a very big hammer for a 
small problem, which will surely interact with other features, and will 
likely call for follow-ons of its own.

 - Some sort of bespoke "block expression for switch".

On the latter, one obvious choice is something lambda-like:

    case 1 -> 1;
    case 2 -> { println("two"); return 2; }

You might argue that this is familiar because it's using `return` just 
like lambda, but ... yuck.  Lambdas are their own invocation scope, so 
`return` can be twisted into making sense, but the block of a switch is 
not, so `return` is definitely the wrong word here.  (Arguably it was 
the wrong word for lambdas too; had someone suggested `break` at the 
right time back then I would probably have been pretty compelled by this 
suggestion, but we picked `return` early (when we were still caught up 
in "lambdas are sugar for inner classes") and didn't look back.  Oh 
well.) But it really seems like a bridge too far to use `return` here. 
The obvious alternative, then, is ... break:


    case 1 -> 1;
    case 2 -> { println("two"); break 2; }

But that is pretty similar to what we have now, just with braces.  If 
the concern is that we're stretching `break` too far, then this is just 
as bad.


Worse, it has two significant additional downsides:

1.  You can't fall through at all.  (Yes, I know some people think this 
is an upside.)  But real code does use fallthrough, and this leaves them 
without any alternative; it also widens the asymmetry of expression 
switch vs statement switch.  (Combine this with other suggestions that 
widen the asymmetry between pattern and non-pattern switch, and you have 
four switch constructs.  Oops.)


2.  Either you can only use these block expressions in switch, in which 
case people hate us for one reason, or you can use them everywhere, and 
they hate us for another.  (I have a hard time imagining that this 
doesn't run into conflicts with other contexts in which one could use 
break (how could it not), plus, I don't think this is the block 
expression idiom I want in the language anyway.)


So it seems like a half-measure that is worse on nearly every metric.

There might be other alternatives, but I don't see a better one, other 
than deprecating switch and designing a whole new mechanism.  Which, 
while I understand the attraction of, I 

Re: break seen as a C archaism

2018-03-09 Thread Brian Goetz
Did you happen to calculate what percentage was _not_ the "default" 
case?  I would expect that to be a considerable fraction.


On 3/9/2018 5:49 PM, Kevin Bourrillion wrote:
On Fri, Mar 9, 2018 at 1:19 PM, Remi Forax > wrote:


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
  2/ that we should use the lambda syntax with return, even if the
semantics is different from the lambda semantics.

I do not like (1) because i think the expression switch will
become useless


In our (large) codebase, +Louis determined that, among switch 
statements that appear translatable to expression switch, 13.8% of 
them seem to require at least one multi-statement case.






Re: break seen as a C archaism

2018-03-09 Thread Brian Goetz
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