To throw in the mix - how is some kind of pattern match assignment (we referred to as a "let expression" in some of the earlier docs [1]) would change the picture here? In other words, maybe it's overloading `=` which is at odds here, and we need to make it more explicit that this is more akin to an extraction/match?


So, I think "let statements" beat "total casts" to the overload here :)

<background>

Originally, we imagined a "let" statement like:

    let P = e [ else ... ]

where P is an optimistically total pattern.

<digression>

We pulled on the "what can you do in the else, besides throw" string for a while, but didn't love where it ended up.  The obvious thing you might want to do besides throw or return, is to assign to the pattern variables and keep going:

    let Point(var x, var y)
    else { x = 0; y = 0; }

This was messy when pattern variables were implicitly final, but now that we backed away from that siren song (whew), these become simple assignments to DU locals.

</digression>

But over time, the `let` started to rankle, not only as noise, but because it limited the usefulness.  The observation is that (again, now that we backed away from the siren song of pattern variables being final):

    Point p = <expression>

could either be the declaration of a local of type Point, _or_ a pattern match to the total pattern `Point p`.  In other words, what we historically  thought of as local variable declaration, could in fact be generalized to total pattern matching.  Not only is this aesthetically pleasing, but it means we can extend this idiom in two dimensions: other patterns, and other places.

The "other patterns" is straightforward enough:

    Point(var x, var y) = p

The only challenge here is what to do with the remainder, but we've done enough work on characterizing the remainder to be comfortable enough with throwing in those cases (usually NPE.)

The "other places" takes slightly longer to get used to, such as method declarations:

    void foo(Point(var x, var y) p, int z) { ... }

which is equivalent to:

    void foo(Point p, int z) { Point(var x, var y) = p; ... }

and a similar possibility with lambdas:

    (Point(var x, var y) p) -> ...

All of this is to say that (a) the "let" statement structure was getting in the way, and (b) there's a generalization of local declaration struggling to get out here, and (c) pattern totality is the key to it.

</background>

Given all this, it would be justifiable to not require the cast at all!  The pattern `BarImpl b` is total on `Bar` (assuming Bar is abstract and sealed only to BarImpl), by virtue of the rules for totality that are outlined in [1].  So by this interpretation of "local declaration generalizes to total pattern matching", we could just say:

    BarImpl b = bar;

and be done, since the expression `bar` is of type Bar and the pattern `BarImpl b` is optimistically total on Bar.  (If the sealing changes between compile and run time, this could throw CCE.)

But some people might find that uncomfortable, which is how we got here; they want the optimism to be explicit.  (Backing off to "let" seems a strict loser compared to "total cast", since total cast has much more leverage for the same incremental syntactic footprint (and requiring let where things are more obvious will be seen as noise, and the statement-ness of `let` gets in the way of using it in more places.))  If this is too aggressive, we would want to limit the `P = e` form to strict totality, or more likely, totality with only { null } remainder (as this would permit using deconstruction patterns on the LHS.)

The blind cast is surely allowed, but its not something we want to encourage -- and as both you and I have found (perhaps the only people who have yet written significant libraries with sealing?), it is going to be almost irresistible to avoid embedding the blind-cast assumptions in your code.

So this is how I got to a "blind cast with assertion", not unlike an @Override assertion -- cast, but raise a compile-time error if the cast is not optimistically total.  Instanceof and switch learned about totality; perhaps casting should too.







[1] https://github.com/openjdk/amber-docs/blob/master/site/design-notes/type-patterns-in-switch.md

Reply via email to