Call for bikeshed -- break replacement in expression switch

Remi Forax forax at univ-mlv.fr
Thu May 16 11:41:33 UTC 2019


Another possible keyword is 'pass'. 

Rémi 

> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "amber-spec-experts" <amber-spec-experts at 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.)

> [ http://dictionary.com/ | 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.)


More information about the amber-spec-observers mailing list