Re: [External] : Re: Pattern assignment

2022-03-28 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> 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 

Re: [External] : Re: Pattern assignment

2022-03-28 Thread Brian Goetz




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.






## 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.

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.


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.





 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?





 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;
  }


All let statements can be rewritten as instanceof.  Are you arguing that 
the whole idea is silly?




"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 jo

Re: Pattern assignment

2022-03-28 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Friday, March 25, 2022 4:38:52 PM
> Subject: Pattern assignment

> We still have a lot of work to do on the current round of pattern matching
> (record patterns), but let's take a quick peek down the road. Pattern
> assignment is a sensible next building block, not only because it is directly
> useful, but also because it will be required for _declaring_ deconstruction
> patterns in classes (that's how one pattern delegates to another.) What 
> follows
> is a rambling sketch of all the things we _could_ do with pattern assignment,
> though we need not do all of them initially, or even ever.
And obviously (but let state the obvious hidden behind "directly useful") we 
have introduced records as named tuples and _declaring_deconstruction_ is a way 
deconstruct that tuple. 

Construction: 
var x = ... 
var y = ... 
var point = new Point(x, y); 

De-construction 
var point = ... 
__let__ Point(var x, var y) = point; 

> # Pattern assignment

> So far, we've got two contexts in the language that can accommodate patterns 
> --
> `instanceof` and `switch`. Both of these are conditional contexts, designed 
> for
> dealing with partial patterns -- test whether a pattern matches, and if so,
> conditionally extract some state and act on it.

> There are cases, though, when we know a pattern will always match, in which 
> case
> we'd like to spare ourselves the ceremony of asking. If we have a 3d `Point`,
> asking if it is a `Point` is redundant and distracting:

> ```
> Point p = ...
> if (p instanceof Point(var x, var y, var z)) {
> // use x, y, z
> }
> ```

> In this situation, we're asking a question to which we know the answer, and
> we're distorting the structure of our code to do it. Further, we're depriving
> ourselves of the type checking the compiler would willingly do to validate 
> that
> the pattern is total. Much better to have a way to _assert_ that the pattern
> matches.

> ## Let-bind statements

> In such a case, where we want to assert that the pattern matches, and forcibly
> bind it, we'd rather say so directly. We've experimented with a few ways to
> express this, and the best approach seems to be some sort of `let` statement:

> ```
> let Point(var x, var y, var z) p = ...;
> // can use x, y, z, p
> ```

> Other ways to surface this might be to call it `bind`:

> ```
> bind Point(var x, var y, var z) p = ...;
> ```

> or even use no keyword, and treat it as a generalization of assignment:

> ```
> Point(var x, var y, var z) p = ...;
> ```

> (Usual disclaimer: we discuss substance before syntax.)

> A `let` statement takes a pattern and an expression, and we statically verify
> that the pattern is exhaustive on the type of the expression; if it is not, 
> this
> is a
> type error at compile time. Any bindings that appear in the pattern are
> definitely assigned and in scope in the remainder of the block that encloses 
> the
> `let` statement.

> Let statements are also useful in _declaring_ patterns; just as a subclass
> constructor will delegate part of its job to a superclass constructor, a
> subclass deconstruction pattern will likely want to delegate part of its job 
> to
> a superclass deconstruction pattern. Let statements are a natural way to 
> invoke
> total patterns from other total patterns.
yes ! 

>  Remainder

> Let statements require that the pattern be exhaustive on the type of the
> expression.
> For total patterns like type patterns, this means that every value is matched,
> including `null`:

> ```
> let Object o = x;
> ```

> Whatever the value of `x`, `o` will be assigned to `x` (even if `x` is null)
> because `Object o` is total on `Object`. Similarly, some patterns are clearly
> not total on some types:

> ```
> Object o = ...
> let String s = o; // compile error
> ```

> Here, `String s` is not total on `Object`, so the `let` statement is not 
> valid.
> But as previously discussed, there is a middle ground -- patterns that are
> _total with remainder_ -- which are "total enough" to be allowed to be
> considered
> exhaustive, but which in fact do not match on certain "weird" values. An
> example is the record pattern `Box(var x)`; it matches all box instances, even
> those containing null, but does not match a `null` value itself (because to
> deconstruct a `Box`, we effectively have to invoke an instance member on the
> box, and we cannot invoke instance members on null receivers.) Similarly, the
> pattern `Box(Bag(String s))` is total on `Box>`, with remainder
> `null` and `Box(null)`.

> Because `let` statements guarantee that its bindings are definitely assigned
> after the `let` statement completes normally, the natural thing to do when
> presented with a remainder value is to complete abruptly by reason of 
> exception.
> (This is what `switch` does as well.) So the following statement:

> ```
> Box> bbs = ...
> let Box(Bag(String s)) = bbs;
> ```

> would throw when encountering `null` or `Box(null)` (bu