We’ve probably pretty much explored the options at this point; time to converge around one of the choices...
> > De: "Brian Goetz" <brian.go...@oracle.com> > À: "amber-spec-experts" <amber-spec-experts@openjdk.java.net> > Envoyé: Dimanche 12 Mai 2019 21:38:38 > Objet: Call for bikeshed -- break replacement in expression switch > As mentioned in the preview mail, we have one more decision to make: the new > spelling of “break value” in expression switches. We have previously > discussed “break-with value”, which everyone seems to like better than “break > value”, but I think we can, and should, do better. > > (Despite the call-for-bikeshed, this is not to reopen every sub-decision — > the 2x2 semantics, the use of ->, the name of the construct — this bikeshed > only has room for one bike.) > > There are two primary reasons why we prefer break-with to break. We > originally chose “break value" when we had a more limited palette of options > to choose from (the keyword-resupply ship hadn’t yet docked.) The > overloading of break creates uncomfortable interactions. There is the > obvious ambiguity between “break value” and “break label”; there is also the > slightly less obvious interaction where we cannot permit “break value” inside > a loop or statement switch inside an expression switch. While both of these > can be “specified around”, they create distortions in the spec, which in turn > creates complexity in the user model; these are a sign that we may be pushing > something a bit too far. Further, historically “break” has been a straight > transfer of control; this muddies up what “break” means. > > Once we alit on the idea of break-* as a keyword, it seemed immediately more > comfortable to make a new break-derived keyword; this allowed us to undo the > distortions that “break value” introduced, and it immediately felt better. > But I think we can do better still. Here’s what’s making me uncomfortable. > > We’ve actually been here before: lambda expressions were the first time we > allowed an expression to contain statements, and while the streamlined case > of “x -> e” didn’t require any control statements, and many lambdas could be > expressed with this form, statement lambdas needed a way to say “stop > executing the body of this lambda, and yield a value.” We settled — somewhat > uncomfortably — on “return value" for this. > > Fast-forward to today, when we’re introducing the second expression form that > can contain statements, and we face the same question: how to indicate “I’m > done, I’m completing normally, here’s my value.” Lambdas provide no help > here; we can’t use “return” here. (Well, we could, but that would be > terrible, so we’re not going to.) Which means we have to solve the problem > again, but differently. That’s already not so great. > > Digression: What’s so terrible about “return”, any why is it OK for lambdas > but not OK for switches? > > While we could of course define “return” to mean whatever we want, But, in > imperative languages with the concept of “methods” or “procedures”, including > Java, return has always had a clear meaning: unwind the current call frame, > and yield the designated value to the caller. Lambda expressions are > effectively method bodies (lambdas are literals for functional interfaces, > which are single method interfaces), and so return (barely) fits. But switch > expressions are most definitely not methods, and are not associated with call > frames. Asking users to look at the enclosing context when they see a > “return” in the middle of a method, to know whether it returns from the > method or merely transfers control within the method, is a lot to ask. (Yes, > I know lambdas ask this as well; this is why this was an uncomfortable > choice, and having made this hole, I’m not anxious to expand it dramatically. > If anything I’d prefer to close it, but that’s another bikeshed.). > > (end digression) > > > We could surely take “break-with” and move on; it feels sufficiently > “switchy”. But let’s look ahead a little bit. We’ve now confronted the same > problem twice: an expression form that, in a minority use case, needed a way > to express “stop computing this expression, because I’m done, and here’s its > value.” (And, unfortunately, we have two different syntactic ways to > express the same basic concept.) Let’s call these “structured expressions.” > > > We have two structured expression forms, and of the three numbers in computer > science, “two” is not one of them. Which suggests we are going to face this > problem again some day — whether it be “block expressions”, or “if > expressions”, or “let expressions”, or “try expressions”, or whatever. (NB: > this call-for-bikeshed most definitely does not extend to “why not just do > generalized block expressions”, so please don’t go there. That said, you > could treat this discussion as “if Java had block expressions, what might > they look like?” But we’re focusing on the content of the block, not how the > block is framed.) > > Let’s say for sake of argument that we might someway want to extend ternary > expressions to support the same kind of “restricted block expressions” as > expression switches. (This is just an example for purposes of illustration, > let’s not get derailed on “but you should use an ‘if’ statement for that"). > > String s = (foo != null) > ? s > : { > println(“null again at line” + __LINE__); > break-with “null”; > }; > > Such an expression needs a way to say “I’m done, here’s my value”, just as > lambda and switch did before it. Clearly “return” is not the right thing here > any more than it is for switches. And I don’t think “break-with” is all that > great here either! It’s not terrible, but outside of a loop or switch, it > starts to feel kind of forced. And it would be terrible to solve this > problem twice with one-time solutions, and have no general story, and then > have to come up with YET ANOTHER way of expressing the same basic concept. > So regardless of what we expect for future expression forms, let’s examine > what our options are that are not tied to call frames (return) or direct > transfer of control (switches and loops.). > > 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. > > I think we can dispatch all but the first relatively easily: > > - We don’t use operators for “return”, we use a keyword; this would be both > a gratuitous departure, as well as too easy to miss. > - Switch expressions don’t have names, and even if we assigned to “switch”, > it wouldn’t be obvious that we were actually terminating execution of the > block. > - 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. > > So, we want a keyword (or contextual keyword.). In some hallway > brainstorming, candidates that emerged include yield, produce, offer, > offer-up, result, value-break, yield-value, provide, resulting-in, > break-with, resulting, yielding, put, give, giving, ... > > (Also to keep in mind: remember we’re dealing with a minority case; most of > the time, there’ll just be an expression on the RHS.) > > TL;DR: I think we might come to regret break-* just as we did with return — > because it won’t scale to future demands we place on it, and having *three* > ways to say basically the same thing in three different contexts would be > embarrassing. I would like to see if we can do better. > > > Of the options listed here, I have a favorite: yield. (This is one of the > terms we’ve actually be using all along when describing this feature in > english.) > > There is one obvious objection to “yield”, which I’d like to preemptively > address: that in some languages (though not in Java, except for the > infrequently-used Thread.yield()), it is associated with concurrency > primitives, such as generators. (This was the objection raised when yield > was proposed in the context of lambdas.). But, these association are not > grounded in existing Java constructs (and, the progress of Loom suggests that > constructs like async/await are not coming to Java, and even if we wanted > language support for generators, there are ample other ways to say it.) > > Dictionary.com <http://dictionary.com/> lists the following meanings for > yield: > > verb (used with object) > - to give forth or produce by a natural process or in return for cultivation: > - to produce or furnish (payment, profit, or interest): > - to give up, as to superior power or authority: > - to give up or over; relinquish or resign: > - to give as due or required: > - to cause; give rise to: > > verb (used without object) > - to give a return, as for labor expended; produce; bear. > - to surrender or submit, as to superior power: > - to give way to influence, entreaty, argument, or the like: > - to give place or precedence (usually followed by to): > - to give way to force, pressure, etc., so as to move, bend, collapse, or > the like: > > These are mostly consistent with the use of “yield” as proposed here. > > One more thing to bear in mind: there is an ordering to abrupt completion > mechanisms, as to how far away they can transfer control: > > - yield: can unwind only the innermost yieldable expression > - break/continue: can unwind multiple control constructs (for, while, > switch), but stays within the method > - return: unwinds exactly one method > - throw: unwinds one or more methods > - System.exit: unwinds the whole VM > > > Bikeshed is open (but remember the bounds of this bikeshed are limited; we’re > talking purely about the syntax of a “stop executing this block and yield a > value to the enclosing context” — and time is ticking.) > > > > >