Semantics of multiple patterns with the same prefix in a switch
Guy Steele
guy.steele at oracle.com
Mon Feb 1 04:56:57 UTC 2021
> On Jan 31, 2021, at 5:57 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> . . .
> Your question, I believe, is whether in such a switch, we may, may not, or must, run the deconstructor twice when we get to the second Box(...) case. We haven't yet written down the rules for this, though of course we've thought about it. As you point out, if the deconstructor has a side-effect, the number of invocations would be visible.
>
> I think there are two realistic options:
>
> - The compiler and runtime _cannot_ try to optimize away the redundant invocation.
> - The compiler and runtime _may_ try to optimize away redundant invocations, but there is no guarantee it does so.
>
> The third option -- that we _must_ do so -- can be quickly seen to becomes unrealistic as patterns get more complicated and harder to analyze. So the first question is do we just give up and guarantee the obvious execution, or do we give ourselves license to be clever, but less deterministic.
There is a fourth option (a variant of the second one), which I believe best conforms to The Java Way of Doing Things, of which WORA is theoretically a substantial part:
- The compiler and runtime _may_ try to optimize away redundant invocations (but there is no guarantee it does so), _provided_ it can prove that such optimization cannot result in observable behavioral differences in execution (appropriately defined).
This principle allows, for example, evaluation of the expression `(n+1)*(n+1)` to perform only one execution of `n+1` (where `n` is of primitive type). Special knowledge about libraries allows `Math.max(x,y)*Math.max(x,y)` to be treated in similar fashion if desired. But in general this cannot be done for `User.method(x)*User.method(x)`.
In the general case of user-defined patterns, the compiler may not be able to prove this. At the other extreme, in the specific case of deconstructors automatically provided for records, it may always be safe to perform such optimizations (we should look at this). In between, maybe some flow analysis would allow a simple proof of safety for some common cases of user-defined deconstructors or patterns.
So in Rémi’s example, the compiler may well be able to get away with calling the Box deconstructor only once if Box is a record type with default deconstructor, but almost certainly cannot if Box has a user-coded deconstructor that has had a side effect inserted.
> Note that nested translation you wrote down is _not_ the obvious one; it is already a "clever" one. THe obvious if-else chain is:
>
> if (object instanaceof Box(Circle circle)) { ... }
> else if (object instanceof Box(Square square)) { ... }
I agree.
> which can be further unrolled to:
>
> if (object instanaceof Box(Shape s) && s instanceof Circle circle) { ... }
> else if (object instanceof Box(Shape s) && s instanceof Square square) { ... }
Yes.
> ...
>
> But, we're already in the weeds of translation details and optimization. A much simpler question that has to be answered first is: are we even willing to say "side effects in patterns can't be relied on to happen with the obvious arity and order, and if you have them, good luck to you"? Similarly, if we have a nested pattern P(Q, R), do we promise that the Q and R tests happen left to right, or let the compiler do what it likes?
I believe that WORA would dictate that, yes, pattern invocations, like expressions, must appear to be evaluated in a well-defined, predictable order, and the standard order in Java is left-to-right.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210131/485df427/attachment-0001.htm>
More information about the amber-spec-experts
mailing list