On May 13, 2019, at 12:08 PM, Guy Steele <guy.ste...@oracle.com> wrote: > >> On May 13, 2019, at 10:28 AM, Doug Lea <d...@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