Re: Pattern matching: next steps after JEP 405

2022-05-20 Thread Brian Goetz
You are right that varargs records are dancing on the edge of a cliff.  
But (a) we have varargs records, and (b) array/varargs patterns are not 
only for records.


If you're arguing that they are not essential *right now* and can be 
deferred, that's a reasonable argument, but you'd have to actually make 
that argument.


But it seems you are arguing that array and varargs patterns are 
*fundamentally incoherent.*  This argument seems way overblown, and as 
you've seen, overblown arguments are usually counterproductive.






On 5/20/2022 8:46 AM, Remi Forax wrote:





*From: *"Brian Goetz" 
*To: *"amber-spec-experts" 
*Sent: *Wednesday, May 18, 2022 9:18:01 PM
*Subject: *Pattern matching: next steps after JEP 405

JEP 405 has been proposed to target for 19.  But, it has some
loose ends that I'd like to refine before it eventually becomes a
final feature.  These include:

[...]




 -*Varargs patterns. * Records can be varargs, but we have an
asymmetry where we can use varargs in constructors but not in
deconstruction.  This should be rectified.  The semantics of this
is straightforward; given

    record Foo(int x, int y, int... zs) { }

just as

    new Foo(x, y, z1, z2)

is shorthand for

    new Foo(x, y, new int[] { z1, z2 })

we also can express

    case Foo(var x, var y, var z1, var z2)

as being shorthand for

    case Foo(var x, var y, int[] { var z1, var z2 })

This means that varargs drags in array patterns.



Thinking a bit about the varargs pattern, introducing them is not a 
good idea because a varargs record is not a safe construct by default,
- varargs are arrays, and arrays are mutable in Java, so varargs 
records are not immutable by default

- equals() and hashCode() does not work as is too.

The record Foo should be written

  record Foo(int x, int y, ... zs) {
   Foo {
 zs = zs.clone();
   }

   public int[] zs() {
 return zs.clone();
   }

   public boolean equals(Object o) {
    return o instanceof Foo foo && x == foo.x && y == foo.y && 
Arrays.equals(zs, foo.zs);

   }

   public int hashCode() {
 return hash(x, y, Arrays.hashCode(zs));
   }
  }

Given that most people will forget that the default behavior of a 
varargs record is not the right one, introducing a specific pattern 
for varargs record to mirror them is like giving a gentle nudge to 
somebody on a cliff.


Note that, it does not mean that we will not support varargs record, 
because

one can still write either
  case Foo(int x, int y, int[] zs)

or
  case Foo(int x, int y, int[] { int... zs })   // or a similar syntax 
that mix a record pattern and an array pattern


but just that there will be no streamlined syntax for a varargs record.

Rémi



Re: Pattern matching: next steps after JEP 405

2022-05-20 Thread Brian Goetz
I'm sorry, I have no idea what argument you are trying to make.  Start 
from the beginning.


On 5/20/2022 1:27 AM, fo...@univ-mlv.fr wrote:





*From: *"Brian Goetz" 
*To: *"Remi Forax" 
*Cc: *"amber-spec-experts" 
*Sent: *Thursday, May 19, 2022 3:05:07 PM
    *Subject: *Re: Pattern matching: next steps after JEP 405



When you have a type pattern X in a middle of a pattern *and*
you have conversions, then there is an ambiguity,
does instanceof Box(X x) means
  Box(var v) && v instanceof X x
or
  Box(var v) && X x = (X) v;


This is not an ambiguity in the language, it is confusion on the
part of the reader :)

In any case, I'm not following your argument here. 



If you have both a type pattern and allow conversions, you have
  Box(X) is equivalent to Box(var v) && v instanceof Y y && X x = (X) y

How do you find Y ?

And yes, the bar is not only that Y has to be unique for the compiler, 
it has also to be obvious for the human reader too.


Rémi



Re: Pattern matching: next steps after JEP 405

2022-05-20 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, May 18, 2022 9:18:01 PM
> Subject: Pattern matching: next steps after JEP 405

> JEP 405 has been proposed to target for 19. But, it has some loose ends that 
> I'd
> like to refine before it eventually becomes a final feature. These include:

[...] 

> - Varargs patterns. Records can be varargs, but we have an asymmetry where we
> can use varargs in constructors but not in deconstruction. This should be
> rectified. The semantics of this is straightforward; given

> record Foo(int x, int y, int... zs) { }

> just as

> new Foo(x, y, z1, z2)

> is shorthand for

> new Foo(x, y, new int[] { z1, z2 })

> we also can express

> case Foo(var x, var y, var z1, var z2)

> as being shorthand for

> case Foo(var x, var y, int[] { var z1, var z2 })

> This means that varargs drags in array patterns.
Thinking a bit about the varargs pattern, introducing them is not a good idea 
because a varargs record is not a safe construct by default, 
- varargs are arrays, and arrays are mutable in Java, so varargs records are 
not immutable by default 
- equals() and hashCode() does not work as is too. 

The record Foo should be written 

record Foo(int x, int y, ... zs) { 
Foo { 
zs = zs.clone(); 
} 

public int[] zs() { 
return zs.clone(); 
} 

public boolean equals(Object o) { 
return o instanceof Foo foo && x == foo.x && y == foo.y && Arrays.equals(zs, 
foo.zs); 
} 

public int hashCode() { 
return hash(x, y, Arrays.hashCode(zs)); 
} 
} 

Given that most people will forget that the default behavior of a varargs 
record is not the right one, introducing a specific pattern for varargs record 
to mirror them is like giving a gentle nudge to somebody on a cliff. 

Note that, it does not mean that we will not support varargs record, because 
one can still write either 
case Foo(int x, int y, int[] zs) 

or 
case Foo(int x, int y, int[] { int... zs }) // or a similar syntax that mix a 
record pattern and an array pattern 

but just that there will be no streamlined syntax for a varargs record. 

Rémi 


Re: Pattern matching: next steps after JEP 405

2022-05-19 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Thursday, May 19, 2022 3:05:07 PM
> Subject: Re: Pattern matching: next steps after JEP 405

>> When you have a type pattern X in a middle of a pattern *and* you have
>> conversions, then there is an ambiguity,
>> does instanceof Box(X x) means
>> Box(var v) && v instanceof X x
>> or
>> Box(var v) && X x = (X) v;

> This is not an ambiguity in the language, it is confusion on the part of the
> reader :)

> In any case, I'm not following your argument here.
If you have both a type pattern and allow conversions, you have 
Box(X) is equivalent to Box(var v) && v instanceof Y y && X x = (X) y 

How do you find Y ? 

And yes, the bar is not only that Y has to be unique for the compiler, it has 
also to be obvious for the human reader too. 

Rémi 


Re: Pattern matching: next steps after JEP 405

2022-05-19 Thread Brian Goetz




When you have a type pattern X in a middle of a pattern *and* you have 
conversions, then there is an ambiguity,

does instanceof Box(X x) means
  Box(var v) && v instanceof X x
or
  Box(var v) && X x = (X) v;


This is not an ambiguity in the language, it is confusion on the part of 
the reader :)


In any case, I'm not following your argument here.



Re: Pattern matching: next steps after JEP 405

2022-05-19 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, May 18, 2022 11:08:41 PM
> Subject: Re: Pattern matching: next steps after JEP 405

>>> - Primitive patterns. This is driven by another existing asymmetry; we can 
>>> use
>>> conversions (boxing, widening) when constructing records, but not when
>>> deconstructing them. There is a straightforward (and in hindsight, obvious)
>>> interpretation for primitive patterns that is derived entirely from existing
>>> cast conversion rules.
>> When calling a method / constructing an object, you can have several 
>> overloads
>> so you need conversions, those conversions are known at compile time.
>> (Note that Java has overloads mostly because there is a rift between 
>> primitives
>> and objects, if there was a common supertype, i'm not sure overloads will 
>> have
>> carried their own weights.)

>> When doing pattern matching, there is no overloads otherwise you will have to
>> decide which conversions to do at runtime.

> There are no overloads YET, because the only deconstruction patterns are in
> records, and records have only one state description. But that is a 
> short-lived
> state of affairs. When we do declared deconstruction patterns, we will need
> overload selection, and it will surely need to dualize the existing 
> three-phase
> overload selection for constructors (e.g., loose, string, and varargs
> invocation contexts.)
When you have a type pattern X in a middle of a pattern *and* you have 
conversions, then there is an ambiguity, 
does instanceof Box(X x) means 
Box(var v) && v instanceof X x 
or 
Box(var v) && X x = (X) v; 

For the deconstruction pattern, if we have overloads, there is the same kind of 
ambiguity 
does Box(X x) means 
calling Box.deconstructor(X) 
or 
calling Box.deconstructor(Y) with y instanceof X 

So if we want to support overloads, and we may have to, at least to support 
adding components but keeping the class backward compatible, we need to 
introduce new rules to solve that ambiguity. 

Rémi 


Re: Pattern matching: next steps after JEP 405

2022-05-18 Thread Brian Goetz




Inference is also something we will need for pattern assignment

  Box<>(var s) = box;


Yes, it would work the same in all pattern contexts -- instanceof as 
well.  Every pattern context has a match target whose static type is known.






 - *Array patterns. * The semantics of array patterns are a pretty
simple extension to that of record patterns; the rules for
exhaustiveness, applicability, nesting, etc, are a relatively
light transformation of the corresponding rules for record
patterns.  The only new wrinkle is the ability to say "exactly N
elements" or "N or more elements". 



I wonder if we should not at least work a little on patterns on 
collections, just to be sure that the syntax and semantics of the 
patterns on collections and patterns on arrays are not too dissimilar.


This is a big new area; collection patterns would have to be co-designed 
with collection literals, and both almost surely depend on some sort of 
type class mechanism if we want to avoid the feature being lame.  I 
don't think its realistic to wait this long, nor am I aiming at doing 
anything that looks like a generic array query mechanism.  Arrays have a 
first element, a second element, etc; the nesting semantics are very 
straightforward, and the only real question that needs additional 
support seems to be "match exactly N" or "match first N".






 - *Primitive patterns. *This is driven by another existing
asymmetry; we can use conversions (boxing, widening) when
constructing records, but not when deconstructing them.  There is
a straightforward (and in hindsight, obvious) interpretation for
primitive patterns that is derived entirely from existing cast
conversion rules. 



When calling a method / constructing an object, you can have several 
overloads so you need conversions, those conversions are known at 
compile time.
(Note that Java has overloads mostly because there is a rift between 
primitives and objects, if there was a common supertype, i'm not sure 
overloads will have carried their own weights.)


When doing pattern matching, there is no overloads otherwise you will 
have to decide which conversions to do at runtime.


There are no overloads YET, because the only deconstruction patterns are 
in records, and records have only one state description.  But that is a 
short-lived state of affairs.  When we do declared deconstruction 
patterns, we will need overload selection, and it will surely need to 
dualize the existing three-phase overload selection for constructors 
(e.g., loose, string, and varargs invocation contexts.)




Re: Pattern matching: next steps after JEP 405

2022-05-18 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, May 18, 2022 9:18:01 PM
> Subject: Pattern matching: next steps after JEP 405

> JEP 405 has been proposed to target for 19. But, it has some loose ends that 
> I'd
> like to refine before it eventually becomes a final feature. These include:

> - Inference for record patterns. Right now, we make you spell out the type
> parameters for a generic record pattern, such as:

> case Box(String s):

> We also don't allow parameterizations that are not consistent with the match
> target. While this is clear, it is verbose (and gets worse when there is
> nesting), and also, because of the consistency restriction, the
> parameterization is entirely inferrable. So we would like to allow the
> parameters to be inferred. (Further, inference is relevant to GADTs, because 
> we
> may be able to gather constraints on the pattern from the nested patterns, if
> they provide some type parameter specialization.)
Inference is also something we will need for pattern assignment 

Box<>(var s) = box; 

> - Refined type checking for GADTs. Given a hierarchy like:

> sealed interface Node { }
> record IntNode(int i) implements Node { }
> record FloatNode(float f) implements Node { }

> we currently cannot type-check programs like:

>  Node twice(Node n) {
> return switch (n) {
> case IntNode(int x) -> new IntNode(x*2);
> case FloatNode(float x) -> new FloatNode(x*2);
> }
> }

> because, while the match constraints the instantiation of T in each arm of the
> switch, the compiler doesn't know this yet.

> - Varargs patterns. Records can be varargs, but we have an asymmetry where we
> can use varargs in constructors but not in deconstruction. This should be
> rectified. The semantics of this is straightforward; given

> record Foo(int x, int y, int... zs) { }

> just as

> new Foo(x, y, z1, z2)

> is shorthand for

> new Foo(x, y, new int[] { z1, z2 })

> we also can express

> case Foo(var x, var y, var z1, var z2)

> as being shorthand for

> case Foo(var x, var y, int[] { var z1, var z2 })

> This means that varargs drags in array patterns.

> - Array patterns. The semantics of array patterns are a pretty simple 
> extension
> to that of record patterns; the rules for exhaustiveness, applicability,
> nesting, etc, are a relatively light transformation of the corresponding rules
> for record patterns. The only new wrinkle is the ability to say "exactly N
> elements" or "N or more elements".
I wonder if we should not at least work a little on patterns on collections, 
just to be sure that the syntax and semantics of the patterns on collections 
and patterns on arrays are not too dissimilar. 

> - Primitive patterns. This is driven by another existing asymmetry; we can use
> conversions (boxing, widening) when constructing records, but not when
> deconstructing them. There is a straightforward (and in hindsight, obvious)
> interpretation for primitive patterns that is derived entirely from existing
> cast conversion rules.
When calling a method / constructing an object, you can have several overloads 
so you need conversions, those conversions are known at compile time. 
(Note that Java has overloads mostly because there is a rift between primitives 
and objects, if there was a common supertype, i'm not sure overloads will have 
carried their own weights.) 

When doing pattern matching, there is no overloads otherwise you will have to 
decide which conversions to do at runtime. 
Given that one mechanism decide which overload to call at compile time and the 
other decide which patterns to call at runtime, they are not symmetric. 

You can restrict the set of conversions so there is a bijection between the 
classes checked at the runtime and the type the user will write, but it's a new 
mechanism, not an existing symmetry. 

In case of a pattern assignment, this is different because there is only one 
pattern that have to cover all the type, so in that case, conversions are not 
an issues because there is no runtime checks. 

> Obviously there is more we will want to do, but this set feels like what we 
> have
> to do to "complete" what we started in JEP 405. I'll post detailed summaries,
> in separate threads, of each over the next few days.

Rémi