> On Jan 31, 2021, at 5:57 PM, Brian Goetz <brian.go...@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.


Reply via email to