Looking ahead: pattern assignment

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Fri Mar 19 19:20:16 UTC 2021

On 12/03/2021 19:58, Brian Goetz wrote:
> While this is not on the immediate horizon, I think we are ready to 
> put the pieces together for pattern assignment.  I think we now 
> understand the form this has to take, and the constraints around it.
Yes please! :-)
> Just as we were successfully able to unify pattern variables with 
> locals*, I would like to be able to unify pattern assignment with 
> assignment.
> A pattern assignment takes the form:
>     P = e
> where P is a pattern with at least one binding variable that is total 
> (perhaps with remainder) on the type of e.  (If P has some remainder 
> on the type of e, then the assignment will throw NPE or ICCE.)  All 
> bindings in P are in scope and DA for the remainder of the block in 
> which P appears, just as with local variable declaration.
> Pattern assignment should work in all of the following contexts:
>  - Assignment statements: P = e
>  - foreach-loops: for (P : e) { ... }
>  - (optional) try-with-resources: try (P = e) { ... }
>  - (optional) method formals: void m(Point(var x, var y) p) { ... }
>  - (optional) lambda formals: (Point(var x, var y) p) -> { ... }

It is easy (and normal) here, I think, to be a little confused. At the 
beginning I was thinking "boy inference of lambdas with target typing 
AND patterns is gonna make javac cry" - but I think the key to a lot of 
these is that whenever you see a pattern declaration, it's like if you 
had an _explicit_ type. So, while there might be magic at runtime to 
decompose the value into the binding variables, from a static 
perspective, typing the lambda:

Point(var x, var y) -> distance(x, y)

is no different than typing this:

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

Crucially, the pattern provides an explicit type (including generic type 
arguments, at least initially).

 From here I guess the next step would be, for a lambda like this:

Box<String>(String s) -> ...

to avoid the outer `<String>` - but I'm not sure about that step. I 
think if we treat a pattern as a replacement for an explicit type, 
things works nicely and there's a simple user model to explain. If we 
make it too magic, it could be more concise, but also more confusing.

As for try-with-resources, as another email alluded to, I think that 
again the explicit type/pattern replacement gives us a guidance as to 
how to interpret that behavior. It is the outermost type that has to be 
AutoCloseable, and the only thing that will be closed at the end of the 
TWR. As Remi pointed out, this idiom could still be helpful where the 
AutoCloseable has some "structure" that the TWR block wants to access to.

> (And I'm sure I forgot some.)

Exceptions? That seems one of the convenient ones:

catch(AssertionError(String msg)) {
    // use msg

> Minimally, we have to align the semantics of local variable 
> declaration with assignment with that of pattern matching; `T t = e` 
> should have the same semantics whether we view it as a local 
> declaration plus assignment, or a pattern match.  This means that we 
> have to, minimally, align the assignment-context conversions in JLS 
> 5.  (If we wish to support patterns in method/lambda formals, we also 
> have to align the method-invocation context conversions.)
> Early in the game, we explored supporting partial patterns in pattern 
> assignment, such as:
>     let P = e
>     else { ... }
> where the `else` clause must either complete abruptly, or assign to 
> all bindings declared in `P`.  (It wasn't until we unified pattern 
> variables with locals that there was an obvious way to specify the 
> latter.)  While this construct is sound, it is in tension with other 
> uses of pattern assignment:
>  - (syntactic) Its pretty hard to imagine an `else` clause without 
> introducing the assignment with some sort of keyword, such as `let`, 
> but this limits its usefulness in other contexts such as method 
> parameter declarations;
>  - (pragmatic) It just doesn't add very much value; if the else 
> throws, it is no less verbose than an if-else.
I was thinking that maybe another way to get at that is by using 
unchecked exceptions - e.g. if pattern failure raised a well-known 
unchecked exception, then users could have a chance (if they want) at 
looking as to why things failed.

try {
    P p = e;
} catch (...)

The problem with this though is that the handler code is very distant 
from where the failure has happened (unlike in let/else).

And we can't really do:

P p;
try {
   p = e;
} catch (...)

Because the proposed pattern assignment doesn't support some form of 
blank declaration - e.g. a way to say:

Point(int x, int y);
if (...) {
     // assign pattern from here
} else {
    // assign pattern from here

Is this something we view as a limitation?


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210319/975ed8d0/attachment-0001.htm>

More information about the amber-spec-experts mailing list