[External] : Re: Pattern assignment

forax at univ-mlv.fr forax at univ-mlv.fr
Mon Mar 28 22:09:07 UTC 2022


> From: "Brian Goetz" <brian.goetz at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Sent: Monday, March 28, 2022 5:41:10 PM
> Subject: Re: [External] : Re: Pattern assignment

>> There are another different between assignment and _let_, a _let_ creates new
>> fresh local variables (binding) while assignment is able to reuse an existing
>> local variable.

> Correct, the more precise analogy is not to _assignment_, but to _local variable
> declaration with initialization_ (whose semantics are derived from assignment.)

>> In Java, the if statement is used a lot (too much IMO but i don't think we
>> should fight to change that) so it may make sense to be able to reuse an
>> existing local variables.

> Yes, this has come up before. I agree that there are cases where we might want
> this (there's one distinguished case where we almost cannot avoid this), but in
> general, I am pretty reluctant to go there -- I think this is incremental
> complexity (and encouragement of more mutability) with not enough commensurate
> benefit.
My general point is that it's less complex to consider that the semantics should be an _assignment pattern_ instead a _local variable declarations with initialization semantics_ if most (not the let ... in) of semantics variants you are proposing can be express as combinations of assignments + if/else. 

And the "encouragement of more mutability" is a false dichotomy argument because you are conflating the mutation of objects with the mutation of local variables, mutation of objects are visible from the outside (from the user POV) which make those objects harder to debug, mutation of local variables are not visible from the outside, so those are very different beasts but you already know that. 

>>> ## Possible extensions

>>> There are a number of ways we can extend `let` statements to make it more
>>> useful; these could be added at the same time, or at a later time.

>>> #### What about partial patterns?

>>> There are times when it may be more convenient to use a `let` even when we know
>>> the pattern is partial. In most cases, we'll still want to complete abruptly if
>>> the
>>> pattern doesn't match, but we may want to control what happens. For example:

>>> ```
>>> let Optional.of(var contents) = optName
>>> else throw new IllegalArgumentException("name is empty");
>>> ```

>>> Having an `else` clause allows us to use a partial pattern, which receives
>>> control if the pattern does not match. The `else` clause could choose to throw,
>>> but could also choose to `break` or `return` to an enclosing context, or even
>>> recover by assigning the bindings.
>> I don't like that because in that case "let pattern else ..." is equivalent of
>> "if instanceof pattern else ... " with the former being expression oriented and
>> the later statement oriented.
>> As i said earlier, i don't think we should fight the fact that Java is statement
>> oriented by adding expression oriented variations of existing constructs.

> We haven't talked about let expressions yet; this is still a statement.
Okay, i did not expect that. For me let was an expression because it's usually the "raison d'être" of let, being an assignment expression. 

Reusing 'let' here is really confusing. 

> It's a fair point to say that the above example could be rewritten as an
> if-else, and when the else throws unconditionally, we still get the same
> scoping. Or that it can be rewritten as

> if (!(pattern match))
> throw blah

> On the other hand, people don't particularly like having to invert the match
> like this just to get the scoping they want.
If you really want 

> In any case, the real value of the else block is where you want to continue (and
> merge the control flow) with default values of the bindings set in the else
> clause (next section). Dropping "else" makes this extremely messy. And once you
> have else, the rest comes for the ride.
But your proposal do the opposite, you are not dropping the "else" but you are dropping the "then" which is also makes thing messy if you want to assign + call a method. 

One advantage of the "if" is that you can easily add more instructions inside the then branch or the else branch. 
With a let ... else, users will have to jungle between if instanceof/ else and let ... else if they add/remove instruction in the then branch. 

>>> #### What about recovery?

>>> If we're supporting partial patterns, we might want to allow the `else` clause
>>> to provide defaults for the bindings, rather than throw. We can make the
>>> bindings of the
>>> pattern in the `let` statement be in scope, but definitely unassigned, in the
>>> `else` clause, which means the `else` clause could initialize them and continue:

>>> ```
>>> let Optional.of(var contents) = optName
>>> else contents = "Unnamed";
>>> ```

>>> This allows us to continue, while preserving the invariant that when the `let`
>>> statement completes normally, all bindings are DA.
>> It fails if the "then" part or the "else" part need more than one instruction.
>> Again, it's statement vs expression.

> No, it's still a statement. I don't know where you're getting this "statement vs
> expression" thing from?
see above, mostly because i'm cursed with knowledge, there is already a 'let' node in the javac AST. 

>>> #### What about guards

>>> If we're supporting partial patterns, we also need to consider the case where
>>> the pattern matches but we still want to reject the content. This could of
>>> course be handled by testing and throwing after the `let` completes, but if we
>>> want to recover via the `else` clause, we might want to handle this directly.
>>> We've already introduced a means to do this for switch cases -- a `when` clause
>>> -- and this works equally well in `let`:

>>> ```
>>> let Point(var x, var y) = aPoint
>>> when x >= 0 && y >= 0
>>> else { x = y = 0; }
>>> ```
>> It can be re-written using an if instanceof, so i do not think we need a special
>> syntax

>> int x, y;
>> if (!(aPoint instanceof Point(_ASSIGN_ x, _ASSIGN_ y) && x >= 0 && y >= 0)) {
>> x = 0;
>> y = 0;
>> }

By the way, there is a simpler way to write the same thing, 
Point(var x, var y) = aPoint; 
if (x < 0 || y < 0) { 
x = y = 0; 
} 

> All let statements can be rewritten as instanceof. Are you arguing that the
> whole idea is silly?
I'm arguing that that the "if" in Java is a statement that is versatile enough so there is no need to introduce new constructs that are like a "if" but with no way to add another operation than the pattern in the then part. 

If really we have a issue with !instanceof, which i agree is not very readable, i would prefer to keep the "if" and introduce something like _not_match_. But it's too much syntactic sugar and not enough semantics for my taste. 

>> "Let ... in" is useful but i don't think it's related to the current proposal,
>> for me it's orthogonal. We can introduce "let ... in" independently to the
>> pattern assignment idea,
>> and if the pattern assignment is already in the language, then "let ... in" will
>> support it.

> Yes and no. You are correct that we could do either or both independently. But
> it's not my job just to design each feature in a locally optimal way; it's my
> job to ensure that the features we design will fit together when they meet up
> in the future. The fact that the construct generalizes in this way is an
> important part of the design even if we don't plan to do this part now.
The generalization part seems self prophetic to me. If we name both features 'let', then it's a generalization. 

And designing let ... in to be both useful by itself and easy composable with pattern assignment so we get synergy does not seem to be a problem here. 

Rémi 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20220329/1c12e109/attachment-0001.htm>


More information about the amber-spec-experts mailing list