Call for bikeshed -- break replacement in expression switch

John Rose john.r.rose at oracle.com
Mon May 13 19:48:39 UTC 2019


On May 13, 2019, at 12:08 PM, Guy Steele <guy.steele at oracle.com> wrote:
> 
>> On May 13, 2019, at 10:28 AM, Doug Lea <dl at cs.oswego.edu> wrote:
>> 
>> 
>> Having lost (nearly) this argument years ago, I'm not sure why I bother, but ...
>> 
>> On 5/12/19 3:38 PM, Brian Goetz wrote:
>>> 
>>> Looking at what other languages have done here, there are a few broad directions: 
>>> 
>>>  - A statement like “break-with v”, indicating that the enclosing structured expression is completing normally with the provided value.  
>>>  - An operator that serves the same purpose, such as “-> e”.
>>>  - Assigning to some magic variable (this is how Pascal indicates the return value of a function).  
>>>  - Treating the last expression in the block as the result.  
>> 
>> (The last one being "progn", the earliest and arguably still best of these.)
>> 
>>> 
>>> I think we can dispatch all but the first relatively easily: ...
>>> 
>>> 
>>>  - Everywhere else in the language (such as method bodies), you are free to yield up a value from the middle of the block, perhaps from within a control construct like a loop; restricting the RHS of case blocks to put their result last would be a significant new restriction, and would limit the ability to refactor to/from methods. And further, the convention of putting the result last, while a fine one for a language that is “expressions all the way down”, would likely be too subtle a cue in Java.  
>> 
>> Last time around, the last point about subtlety and odd-lookingness of progn seemed to bother people the most.  It is possible to make it  less subtle by additionally requiring some symbol. Prefix "^" is still available. Allowing for example:
>> 
>> 
>>     String s = (foo != null) 
>>         ? s
>>         : { println(“null again at line” + __LINE__);  ^ “null”;  };
>> 
>> Which still lgtm….
> 
> Could be worse, but looks to be like Java with a Smalltalk accent—just as
> 
> 	{ foo(); bar }
> 
> is Java with a Lisp (or ECL) accent.  I would prefer to adapt a bit of syntax from ECL: the statement
> 
> 	b => e;
> 
> evaluates b as a boolean expression, and if it is true, then e is evaluated and its value becomes the value of the block.  This gives you a syntax very similar to that of Lisp COND:
> 
> 	{ x > y => 1; x < y => -1; true => 0; }
> 
> If you then want to further abbreviate “true =>”, well, that’s another story, but I wouldn’t blame you.

OK, I can't resist putting some spray paint here.

If we are contemplating operator-like syntaxes
(instead of the keyword-like ones that seem most
reasonable, and which Brian is guiding us towards),
then let's note that the operator-like syntax that
*Java already has* for producing a value from a
structured expression is "->".  So perhaps the
Java-native idiom for ECL's "true=>" is just "->".
Or (more likely for me) it is a break-like keyword
*with an arrow*.

So under that observation:

switch (x) { case Y -> z; }

is short for something like:

switch (x) { case Y -> { … break -> z; } }

and (what's more) the "…" could contain
side effects and let-bindings.

The rule for developers is that if you
needed to put a {…} block after your
arrow ->, then you can still use an
arrow to return a value, but it must be
an extra arrow, marked with a keyword
(or syntax context) that means "here is
the rest of the arrow you wanted to
write a moment ago".

This could work inside of lambdas also:

f( (x,y) -> z )

is short for something like:

f( (x,y) -> { … return -> z; } )

(Why do such a thing?  To give users the
option of a uniform style which answers
every "->{" with a finishing arrow; they
*can* use unadorned "return" but their
colleagues might frown on the faux pas.)

One reason I'm pushing on the "interrupted
arrow" idea here is a fundamental design
prejudice I have.  I very much like the Lisp
syntax (block foo … (return-from foo x) …).
Although "return" is damaged goods for us,
what I'd like to salvage from this example
is the *very clear correspondence* between
the "starter syntax" of the structured expression
("block foo") and the "stopper syntax" in the
middle ("return-from foo").  The shared
tag "foo" makes it very easy for the eye to
match up the stopper with the starter.
You don't have to consult a complex
matrix of "what matches with what".

("I shot an arrow into the air, and where
it landed only the author of the break
permeability matrix knows here.")

OK, one more spritz of spray paint and
I'm done for now.  If we like the idea of
an "interrupted arrow", then we could
think about going the whole way with it.
If the "stopper" is the sharp end of the
arrow (anchored to a keyword like
return or break) then the "starter"
of the structured expression could be
the dull end of the arrow (without an
arrowhead).  Like this:

switch (x) { case Y -{… break -> z; } }

Here, the rule is if you intend to use an
arrow to return a value, you put half of
the arrow where the return will go to,
and the other half when you have a value.

(Note that the syntax "break LABEL" could
be added easily, later on, if there were
any value for that, which probably there
isn't.)

This conflicts with a bit of precedent with
lambdas, where we might expect to break
the arrows the new way:

f( (x,y) -{ … return -> z; } )

If we don't want broken arrows then set up
a duel between the starter and stopper
with opposing arrows:

switch (x) { case Y -> {… break <- z; } }

Or let the author propose a target at the starter:

switch (x) { case Y @< {… break -> z; } }

Surely that would be a spritz too far.

— John


More information about the amber-spec-experts mailing list