with and binary backward compatibility

2022-06-14 Thread Remi Forax
Hi all,
Let say we have a Point with 2 components
  record Point(int x, int y) { }

Then we change the record to add a 3rd components in a more or less backward 
compatible way
  record Point(int x, int y, int z) {
Point(int x, int y) {
  this(x, y, 0);  // creation of the new value 0
}
  }

Now, let say there is a 'with' somewhere in another code

  var newPoint = point with { x = 3; };

If this code is compiled when the record Point had only two components, so this 
is equivalent to

  Point(int x, int y) = point;  // i.e. int x = point.x(); int y = point.y();
  x = 3;
  var newPoint = new Point(x, y);

The problem is that if we run that code with the new version of Point (the one 
with 3 components),
newPoint.z is not equals to point.z but to 0, so once there is a 'with' 
somewhere, there is no backward compatibility anymore.

We can try to restore the backward compatibility by compiling to a slightly 
different code using invokedynamic and a desugared method corresponding to the 
body of the 'with'

  var newPoint = invokedynamic (point) [desugared$method];  // equivalent to a 
call using with

  static Point desugared$method(int x, int y, MethodHandle mh) {  // content of 
the body
x = 3;
return mh.invokeExact(x, y);
  }

an at runtime, we generate a tree of method handles that does more or less
  stub(Point point) {
return desugared$method(point.x(), point.y(), (a, b) -> new Point(a, b, 
point.z())
  }

because this code is generated at runtime, it will be always compatible with 
the latest version of Point.

If we want to support this encoding, it means that the local variables of the 
enclosing method need to be effectively final so the body of with can be lifted 
to a private static method (exactly like a lambda).


If we generalize this a bit, we can also use the same trick for the record 
pattern, in that case the pattern Point(int a, int b) is equivalent at runtime 
to Point(int a, int b, _) once the runtime found that the canonical 
deconstructor emits the values of 3 components.
I'm not sure it's a path i want to follow because i would prefer the record 
pattern to match the shape excatly, but i find it more attractive than the idea 
to have overloaded deconstructors.

regards,
Rémi


Re: "With" for records

2022-06-10 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Friday, June 10, 2022 2:44:51 PM
> Subject: "With" for records

> In

> [
> https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md
> |
> https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md
> ]

> we explore a generalized mechanism for `with` expressions, such as:

> Point shadowPos = shape.position() with { x = 0 }

> The document evaluates a general mechanism involving matched pairs of
> constructors (or factories) and deconstruction patterns, which is still 
> several
> steps out, but we can take this step now with records, because records have 
> all
> the characteristics (significant names, canonical ctor and dtor) that are
> needed. The main reason we might wait is if there are uncertainties in the
> broader target.

> Our C# friends have already gone here, in a way that fits into C#, using
> properties (which makes sense, as their language is built on that):

> object with { property-assignments }

> The C# interpretation is that the RHS of this expression is a sort of "DSL",
> which permits property assignment but nothing else. This is cute, but I think
> we can do better.

> In our version, the RHS is an arbitrary block of Java code; you can use loops,
> assignments, exceptions, etc. The only thing that makes it "special" is that
> that the components of the operand are lifted into mutable locals on the RHS.
> So inside the RHS when the operand is a Point, there are fresh mutable locals
> `x` and `y` which are initialized with the X and Y values of the operand. 
> Their
> values are committed at the end of the block using the canonical constructor.
GREAT ! 

I've several questions, that we will have to answer later. 

The block is also special because there is an implicit return at the end ? so i 
believe "return" should be disallowed inside the block, right ? 

Does the lifting that appears at the beginning of the block call all accessors 
? or the compiler try to be clever and not call an accessor when its value is 
not needed. 
For example, in 
point with { y = 3; } 
calling point.y() is useless, or is it something the JIT will take care of ? 

Do we allow "with" followed by an empty block ? As a way to clone a record ? 

About the declaration of local variables, in Java, there is no hiding/shadowing 
between local variables, so a code like this is rejected ? Or do we introduce a 
special rule for the hiding of implicit variables ? 
int x = Integer.parseInt(); 
... 
foo(point with { y = 3; }); // x is declared twice because there is an implicit 
int x = point.x(); 

> This should remind people of the *compact constructor* in a record; the body 
> is
> allowed to freely mutate the special variables (who also don't have obvious
> declarations), and their terminal values determine the state of the record.

> Just as we were able to do record patterns without having full-blown
> deconstructors, we can do with expressions on records as well, because (a) we
> still have a canonical ctor, (b) we have accessors, and (c) we know the names
> of the components.

> Obviously when we get value types, we'll want classes to be able to expose (or
> not) such a mechanism (both for internal or external use).
yes, Complex.default with { re = 3; im = 4; } seems a great fit for value 
classes. 

Rémi 


Re: Simplifying switch labels

2022-06-02 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "daniel smith" , "amber-spec-experts" 
> 
> Sent: Thursday, June 2, 2022 8:08:14 PM
> Subject: Re: Simplifying switch labels

>> In this framing, the restrictions about sets of elements in a single label 
>> don't
>> apply, because we're talking about two different labels. But we have rules to
>> prevent various abuses. Examples:
>>
>> case 23: case Pattern: // illegal before and now, due to fallthrough Pattern
>> rule
> 
> Ideally, the fallthrough rule should be about _bindings_, not
> _patterns_.  If P an Q are patterns with no binding variables, then it
> should be OK to say:
> 
>     case P:
>     case Q:
> 
> The rule about fallthrough is to prevent falling into code where the
> bindings are not DA.
> 
>> Note that the second kind of Pattern SwitchLabel is especially weird—it binds
>> 'null' to a pattern variable, and requires the pattern to be a (possibly
>> parenthesized) type pattern. So it's nice to break it out as its own 
>> syntactic
>> case. I'd also suggest rethinking whether "case null," is the right way to
>> express the two kinds of nullable SwitchLabels, but anyway now it's really
>> clear that they are special.
> 
> This is a painful compromise.  While this may be a transitional
> phenomena, the rule "switch matches null only when `null` appears in a
> case" is a helpful way to transition people away from "switch always
> throws on null."

It also allows to express that a switch statement on an enum should be 
exhaustive by adding
  case null -> throw null;

Rémi


Re: Named record pattern

2022-06-01 Thread Remi Forax
> From: "Brian Goetz" 
> To: "Tagir Valeev" 
> Cc: "amber-spec-experts" 
> Sent: Tuesday, May 31, 2022 6:12:06 PM
> Subject: Re: Named record pattern

> Gavin reminded me that we are not finalizing patterns in switch in 19 (hard to
> keep track, sometimes), so we have a little bit of time to figure out what we
> want here.

> One thing that is potentially confusing is that patterns work indirectly in a
> number of ways. For example, if we have a declared deconstruction pattern for
> Point, you can't *invoke* it as you can a method; the language runtime invokes
> it on your behalf under the right situations. (In this way, a deconstructor is
> a little like a static initializer; it is a body of code that you declare, but
> you can't invoke it directly, the runtime invokes it for you at the right 
> time,
> and that's fine.)

> I had always imagined the relationship with locals being similar; a pattern
> causes a local to be injected into certain scopes, but the pattern itself is
> not a local variable declaration. Obviously there is more than one way to
> interpret this, so we should make a more deliberate decision.

> As a confounding example that suggests that pattern variables are not "just
> locals", in the past we talked about various forms of "merging":

> if (t instanceof Box(String s) || t instanceof Bag(String s)) { ... }

> or

> case Box(String s):
> case Bag(String s):
> common-code;

> If pattern variables could be annotated, then the language would be in the
> position of deciding what happens with

> case Box(@Foo(1) String s):
> case Bag(@Foo(2) String s):

> (This is the "annotation merging" problem, which is why annotations are not
> inherited in the first place.)

> I don't have an answer here, but I'm going to think about the various issues 
> and
> try to capture them in more detail before proposing an answer.
For me, there is a difference between a binding and a local variable, is that 
bindings are the one declared inside a pattern and a local variables is how a 
binding is transformed to be usable inside the boby if the pattern match. 
So we can merge bindings and as a result have one local variable to be used in 
the body. 

About the annotations, if we follow the data orientation principle (data is 
more important than code), we can have a record 
record Person(@NonNull String name) { } 

and a record pattern 
case Person(@NonNull String s) -> ... 

We want to be able to declare @NonNull in the record pattern so if the data is 
changed, if the component name of the record becomes nullable by example, the 
pattern will fail to compile. 
So i think we should allow annotations because it's a way to enforce that data 
are more important that code. 

As you said, annotation merging is an issue because the correct merging 
requires to know the semantics of the annotations and the compiler has no way 
to know that. 
But there is a simple solution, annotation are trees, so they can be compared 
structurally. Thus we can do bindings merging if the annotations are the same 
structurally. 

regards, 
Rémi 


Re: Named record pattern

2022-05-31 Thread Remi Forax
> From: "Maurizio Cimadamore" 
> To: "Brian Goetz" , "Tagir Valeev" 
> Cc: "amber-spec-experts" 
> Sent: Tuesday, May 31, 2022 7:41:21 PM
> Subject: Re: Named record pattern

> While merging is an issue (if supported), I think early in the design we 
> settled
> on the principle that a binding variable should always only have one
> corresponding declaration (in the places where it is defined).

> So, from a language perspective, I don’t see an immediate problem, in the 
> sense
> that the annotations and finality for “s” would come from the place where “s”
> has been declared.

> From a compiler perspective, the big question is whether, even in the absence 
> of
> merging in the flow scoping rules, we could still perform merging under the
> hood, in order to speed up computation. Consider:
> sealed interface Node
> record Pair (Node fst, Node snd) { } record A (String s) implements Node {}
> record B ( int i) implements Node {}

> And then:
> case Pair (A(String s1) , A (String s2) ) -> ... case Pair (A(String s3) , B (
> int i1) ) -> ... case Pair (B( int i2) , A (String s4) ) -> ... case Pair (B(
> int i3) , B ( int i4) ) -> ...

> (for simplicity, all binding names are different, so that we do not depend on
> whether the block after -> … completes normally or not)

> Of course the first two patterns share the common subpattern A(String s1) (if
> you ignore naming differences). So it might be possible, in principle, for the
> compiler/runtime to generate an optimized decision tree in which the String
> value for the first A(…) sub-pattern is computed only once, and then shared in
> the two cases. (A similar reasoning applies to the last two patterns).

> But if the types in the first and second pattern could be annotated 
> differently,
> then we are faced with a number of challenges, as the compiler would not be
> able to just “blindly” reuse the cached values (as those values would be
> shared, and, therefore, unannotated). Instead, the compiler would have to
> assign the cached value into a fresh , correctly annotated local variable that
> is used only inside the given case . This is not impossible of course, but 
> adds
> complexity to the translation strategy, and/or might affect the number of 
> moves
> we might be able to do.

The same issue arise if the patterns parts are generated at runtime using an 
invokedynamic. 

I don't think it's an issue if we consider that inside the pattern tree, we 
have bindings and that once "->" is crossed, those bindings are materialized as 
local variables with annotations. 

I kind of easier to see it if there an invokedynamic and a carrier object. A 
switch(value) is translated to 
Object carrier = invokedynamic pattern_match(value) [send the patterns as a 
constantpool constant]; 
int index = invokdynamic extract (carrier)[binding 0 /* index */]; 
switch(index) { 
case 0 -> { 
// here we rematerialize the local variables 
String s1 = invokdynamic extract (carrier)[binding 1]; 
String s2 = invokdynamic extract (carrier)[binding 2]; 
... 
} 
case 1 -> { 
// we materialize s3 and i 
String s3 = invokdynamic extract (carrier)[binding 1]; 
int i1 = invokdynamic extract (carrier)[binding 2]; 
... 
} 
... 
} 

In a sense, this is similar to a lambda, it's not the parameter of the method 
of the lambda proxy which is annotated but the parameter of the static method 
desugared from the lambda body. 
Here, it's not the patterns part (that one that matches) which store the 
annotations but the part that extract the value from the carrier into local 
variables that stores the annotation. 

I think this idea also work if we do not use invokedynamic, the matching par 
can use a Tree or a DAG that fuses several bindings to one, it's not an issue 
if we generate local variables initialized from the binding values afterward. 

regards 

> Maurizio

Rémi 

> On 31/05/2022 17:12, Brian Goetz wrote:

>> As a confounding example that suggests that pattern variables are not "just
>> locals", in the past we talked about various forms of "merging":

>> if (t instanceof Box(String s) || t instanceof Bag(String s)) { ... }

>> or

>> case Box(String s):
>> case Bag(String s):
>> common-code;

>> If pattern variables could be annotated, then the language would be in the
>> position of deciding what happens with

>> case Box(@Foo(1) String s):
>> case Bag(@Foo(2) String s):

>> (This is the "annotation merging" problem, which is why annotations are not
>> inherited in the first place.)
> ​


It's the data, stupid !

2022-05-30 Thread Remi Forax
Hi all,
i think the recent discussions about the pattern matching are too much about 
details of implementation and i fear we are losing the big picture, so let me 
explain why i (we ?) want to add pattern matching to Java.

Java roots is OOP, encapsulation serves us well for the last 25+ years, it 
emphasis API above everything else, data are not important because it's just a 
possible implementation of the API.

But OOP / encapsulation is really important for libraries, less for 
applications. For an application, data are more important, or at least as 
important as API.

The goal of pattern matching is make data the center of the universe. Data are 
more important than code, if the data change because the business requirements 
change, the code should be updated accordingly. Pattern matching allows to 
write code depending on the data and it will fail to compile if the data 
change, indicating every places in the code where the code needs to be updated 
to take care of the new data shape.

The data can change in different ways, 
 1) a new kind of a type (a subtype of an interface) can be introduced, we have 
added sealed types and make switch on type exhaustive so if a developer add a 
new subtype of an interface, the compiler will refuse to compile all patterns 
that are not exhaustive anymore, indicating that the code must be updated.
 2) a data can have a new field/component, we have introduced record pattern 
that match the exact shape of a record, so if a developer add a new component, 
the compiler will refuse to compile the record pattern with a wrong shape 
indicating that the code must be updated.

So experts, do you agree that this is what we want or did i miss something ?

Rémi

PS: the title is a nod to James Carville.





Guard variable and being effectively final

2022-05-21 Thread Remi Forax
Not sure if it's an implementation bug (bad error message from the compiler) or 
a spec bug,
hence this message to both amber-dev and amber-spec-experts.

If i try to compile this code with Java 19 (which currently still uses && 
instead of when for a guard)

interface reverse_polish_notation {
  static Map OPS =
  Map.of("+", (a, b) -> a + b, "*", (a, b) -> a * b);

  static int eval(List expr) {
var stack = new ArrayDeque();
for(var token: expr) {
  final IntBinaryOperator op;
  stack.push(switch (token) {
case String __ && (op = OPS.get(token)) != null -> {
  var value1 = stack.pop();
  var value2 = stack.pop();
  yield op.applyAsInt(value1, value2);
}
default -> Integer.parseInt(token);
  });
}
return stack.pop();
  }

  static void main(String[] args) {
var expr = List.of("1",  "2",  "+", "3", "*", "4");
System.out.println(eval(expr));
  }
}

I get the following error

java --enable-preview --source 19 reverse_polish_notation.java
reverse_polish_notation.java:17: error: local variables referenced from a guard 
must be final or effectively final
case String __ && (op = OPS.get(token)) != null -> {
   ^
Note: reverse_polish_notation.java uses preview features of Java SE 19.
Note: Recompile with -Xlint:preview for details.
1 error
error: compilation failed
  
Obviously the error message is funny, IntBinaryOperator is declared final so it 
is effectively final.

In case of a lambda,
  final IntBinaryOperator op;
  Supplier supplier = () -> op = null;

supplier.get() can be called several times so "op = null" does not compile.

But in the example above, "op" can not be assigned more than once so maybe it 
should compile.

regards,
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-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 


case null / null pattern (v2)

2022-04-18 Thread Remi Forax
I've found a way to encode the null pattern if you have a record

record Foo(int x) { }

Foo foo = ...
return switch(foo) {
  case Foo(int _) foo -> "i'm a foo not null here !";
  case Foo fooButNull -> "i can be only null here !"; 
};

I wonder if allowing those two patterns, a record pattern and a type pattern 
using the same type is a good idea or not, it seems a great way to obfuscate 
thing.

Rémi


Record pattern and side effects

2022-04-17 Thread Remi Forax
This is something i think we have no discussed, with a record pattern, the 
switch has to call the record accessors, and those can do side effects,
revealing the order of the calls to the accessors.

So by example, with a code like this

  record Foo(Object o1, Object o2) {
public Object o2() {
  throw new AssertionError();
}
  }

  void int m(Foo foo) {
return switch(foo) {
  case Foo(String s, Object o2) -> 1
  case Foo foo -> 2
};
  }

  m(new Foo(3, 4));   // throw AssertionError ?

Do the call throw an AssertionError ?
I believe the answer is no, because 3 is not a String, so Foo::o2() is not 
called.

Rémi


case null vs null pattern

2022-04-16 Thread Remi Forax
Hi all,
i maybe wrong but it seems that the spec consider null as a kind of case 
instead of as a kind of pattern, which disables the refactoring that should be 
possible with the introduction of the record pattern.

Let suppose i have a sealed type with only one implementation declared like this

  sealed interface I {
record A() implements I { }
  }

if null is part of the set of possible values, i have switches like this

  switch(i) {
case null, A a -> // do something with 'a' here
  }


Now we are introducing record pattern into the mix, so i can have a Box of I,
  record Box(I i) { }

the problem is that i can not write
  switch(box) {
case Box(A|null a) -> // do something with 'a' here
  }
  
because null is handled as a kind of case instead of as a kind of a null 
pattern.

Should we introduce a null pattern instead of having a specific "case null" ?
(and disallow the null pattern in an instanceof ?)

regards,
Rémi


Re: Evolving past reference type patterns

2022-04-16 Thread Remi Forax
> From: "Guy Steele" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Saturday, April 16, 2022 4:10:06 AM
> Subject: Re: Evolving past reference type patterns

> Yes, this is a clear improvement to the example code.

> That said, I am always (or at least now) a bit leery of language designers
> motivating a new language feature by pointing out that it would make a 
> compiler
> easier to write. As I have learned the hard way on more than one language
> project, compilers are not always representative of typical application code.
> (Please consider this remark as only very minor pushback on the form of the
> argument.)

This is a very specific example due to the way integers are encoded in the 
bytecode, 
also you can simplify the code a bit more because ICONST_M1, ICONST_0, etc are 
all subsequents. 

Moreover, as i said earlier, it's more a work for method patterns. 
If we suppose we have a static pattern method isByte in java.lang.Byte 

class Byte { 
static pattern (byte) isByte(int value) { 
if (value >= -128 && value <= 127) { 
return match (short) value; 
} 
return no-match; 
} 
} 

the code becomes 

return with(switch (value) {
case -1 .. 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1 + 
value); 
case Byte.isByte(byte _) -> ConstantInstruction.ofArgument(Opcode.BIPUSH, 
value);
case Short.isShort(short _) -> 
ConstantInstruction.ofArgument(Opcode.SIPUSH, value);
default -> ConstantInstruction.ofLoad(Opcode.LDC, 
BytecodeHelpers.constantEntry(constantPool(), value));
}); 

Rémi 

>> On Apr 15, 2022, at 5:36 PM, Brian Goetz < [ mailto:brian.go...@oracle.com |
>> brian.go...@oracle.com ] > wrote:

>>>* asking if something fits in the range of a byte or int; doing this by 
>>> hand is
>>> annoying and error-prone
>>>* asking if casting from long to int would produce truncation; doing 
>>> this by
>>> hand is annoying and error-prone

>> Here’s some real code I wrote recently that would benefit dramatically from
>> this:
>> default CodeBuilder constantInstruction(int value) {
>> return with(switch (value) {
>> case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1);
>> case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0);
>> case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1);
>> case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2);
>> case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3);
>> case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4);
>> case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5);
>> default -> {
>> if (value >= -128 && value <= 127) {
>> yield ConstantInstruction.ofArgument(Opcode.BIPUSH, value);
>> }
>> else if (value >= -32768 && value <= 32767) {
>> yield ConstantInstruction.ofArgument(Opcode.SIPUSH, value);
>> }
>> else {
>>yield ConstantInstruction.ofLoad(Opcode.LDC,
>> BytecodeHelpers.constantEntry(constantPool(), value));
>> }
>> }
>> });
>> }

>> could become the less error-prone and uniform:
>> default CodeBuilder constantInstruction(int value) {
>> return with(switch (value) {
>> case -1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_M1);
>> case 0 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_0);
>> case 1 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_1);
>> case 2 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_2);
>> case 3 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_3);
>> case 4 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_4);
>> case 5 -> ConstantInstruction.ofIntrinsic(Opcode.ICONST_5);
>> case byte value -> ConstantInstruction.ofArgument(Opcode.BIPUSH, 
>> value);
>> case short value -> ConstantInstruction.ofArgument(Opcode.SIPUSH, 
>> value);
>>default -> ConstantInstruction.ofLoad(Opcode.LDC,
>> BytecodeHelpers.constantEntry(constantPool(), value));
>> });
>> }
>> ​


Re: Evolving past reference type patterns

2022-04-15 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Friday, April 15, 2022 10:50:27 PM
> Subject: Evolving past reference type patterns

[...] 

> There are also some small differences between the safe cast conversions and
> method invocation context. There is the same issue with unboxing null (throws
> in (loose) invocation context), and method invocation context makes no attempt
> to do narrowing, even for literals. This last seems mostly a historical wart,
> which now can’t be changed because it would either potentially change (very
> few) overload selection choices, or would require another stage of selection.

It's not an historical wart, it's careful design (or co-design between the 
language and the VM). 
Assignments can be used for fields, that are stored on heap, while method 
arguments can only be stored on stack. 
Values on stack/in registers are either 32 bits or 64 bits while values on the 
heap are multiple of 8 bits. 

That's the same reason why the result of the addition of two shorts is an int 
and why assignment conversions in the context of a computation (the pattern 
matching helps to express computation) feels weird. 

> What are the arguments against this interpretation? They seem to be various
> flavors of “ok, but, do we really need this?” and “yikes, new complexity.”

> The first argument comes from a desire to treat pattern matching as a
> “Coin”-like feature, strictly limiting its scope. (As an example of a similar
> kind of pushback, in the early days, it was asked “but does pattern matching
> have to be an expression, couldn’t we just have an “ifmatch” statement? (See
> answer here: [
> http://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html |
> http://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html ] )
> This is the sort of question we get a lot — there’s a natural tendency to try
> to “scope down” features that seem unfamiliar. But I think it’s
> counterproductive here.

We all know that this is a big feature, don't worry. 

> The second argument is largely a red herring, in that this is not new
> complexity, since these are exactly the rules for successful casts. In fact,
> not doing it might well be perceived as new complexity, since it results in
> more corner cases where refactorings that seem like they should work, do not,
> because of conversions.
> ​

It is new complexity, the rules for primitive narrowing are brand new. 

Can you provides examples of such refactorings ? 

Rémi 


Re: Primitive type patterns

2022-04-07 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Thursday, April 7, 2022 9:54:11 PM
> Subject: Re: Primitive type patterns

> There's another, probably stronger, reason why primitive patterns supporting
> widening, narrowing, boxing, unboxing, etc, are basically a forced move,
> besides alignment with `let` statements, discussed earlier:

>> There is another constraint on primitive type patterns: the let/bind 
>> statement
>> coming down the road. Because a type pattern looks (not accidentally) like a
>> local variable declaration, a let/bind we will want to align the semantics of
>> "local variable declaration with initializer" and "let/bind with total type
>> pattern". Concretely:

>> let String s = "foo";

>> is a pattern match against the (total) pattern `String s`, which introduces 
>> `s`
>> into the remainder of the block. Since let/bind is a generalization of local
>> variable declaration with initialization, let/bind should align with locals
>> where the two can express the same thing. This means that the set of
>> conversions allowed in assignment context (JLS 5.2) should also be supported 
>> by
>> type patterns.

> The other reason is the duality with constructors (and eventually, methods). 
> if
> we have a record:

> record R(int x, Long l) { }

> we can construct an R with

> new R(int, Long) // no adaptation
> new R(Integer, Long) // unbox x
> new R(short, Long) // widen x
> new R(int, long) // box y

> Deconstruction patterns are the dual of constructors; we should be able to
> deconstruct an R with:

> case R(int x, Long l) // no adaptation
> case R(Integer x, Long l) // box x
> case R(short s, Long l) // range check
> case R(int x, long l) // unbox y, null check

> So the set of adaptations in method invocation context should align with those
> in nested patterns, too.
We already discussed those rules when we discuss instanceof, it means that "x 
instanceof primitive" has different meaning depending on the type of x 
Object x = ... 
if (x instanceof short) { ... } // <=> instanceof Short + unboxing 

int x = ... 
if (x instanceof short) { ... } // <=> range check 

Byte x = ... 
if (x instanceof short) { ... } // <=> nullcheck + unboxing + widening 

You are overloading instanceof with different meanings, losing everybody apart 
the compiler in the process. 

It's also another creative way to have an action at distance, 
var x = ... 
f (x instanceof short) { ... } // <=> ??? 

We do not need all theses conversions to be part of the pattern, those 
conversions are already done as part expression/instruction after the ':' or 
'->' of the switch in an organic way. 
// with op(int, Long) 
case R(int x, Long l) -> op(x, l); // no adaptation 

// with op(Integer, Long) 
case R(int x, Long l) -> op(x, l); // box x 

// with op(int, long) 
case R(int x, Long l) -> op(x, l); // unbox l, nullcheck 

And for the range check, as i said earlier, it's better to use a pattern method 
with a good name so everybody will be able to read the code. 

Rémi 


Re: [External] : Re: Remainder in pattern matching

2022-03-30 Thread Remi Forax
- Original Message -
> From: "Dan Heidinga" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, March 30, 2022 8:43:42 PM
> Subject: Re: [External] : Re: Remainder in pattern matching

> On Wed, Mar 30, 2022 at 2:38 PM Brian Goetz  wrote:
>>

[...]

> 
> And from your other response:
> 
>> Another thing it gains is that it discourages people
>> from thinking they can use exceptions in dtors; having these laundered
>> through MatchException discourages using this as a side channel, though
>> that's a more minor thing.
> 
> This is a stronger argument than you give it credit for being.
> Wrapping the exception adds a bit of friction to doing the wrong thing
> which will pay off in helping guide users to the intended behaviour.

Wrapping exceptions into a MatchException seems a very bad idea to me.
When you compute something on an AST, the pattern matching is recursive, so if 
an exception occurs, instead of having one exception with a long stacktrace, we 
will get a linked list of MatchException with each of them having a long 
stacktraces.

> 
> --Dan

Rémi

> 
>> On 3/30/2022 2:12 PM, Dan Heidinga wrote:
>>
>> The rules regarding NPE, ICCE and MatchException look reasonable to me.
>>
>>
>> As a separate but not-separate exception problem, we have to deal with at 
>> least
>> two additional sources of exceptions:
>>
>>  - A dtor / record acessor may throw an arbitrary exception in the course of
>>  evaluating whether a case matches.
>>
>>  - User code in the switch may throw an arbitrary exception.
>>
>> For the latter, this has always been handled by having the switch terminate
>> abruptly with the same exception, and we should continue to do this.
>>
>> For the former, we surely do not want to swallow this exception (such an
>> exception indicates a bug).  The choices here are to treat this the same way 
>> we
>> do with user code, throwing it out of the switch, or to wrap with
>> MatchException.
>>
>> I prefer the latter -- wrapping with MatchException -- because the exception 
>> is
>> thrown from synthetic code between the user code and the ultimate thrower,
>> which means the pattern matching feature is mediating access to the thrower. 
>>  I
>> think we should handle this as "if a pattern invoked from pattern matching
>> completes abruptly by throwing X, pattern matching completes abruptly with
>> MatchException", because the specific X is not a detail we want the user to
>> bind to.  (We don't want them to bind to anything, but if they do, we want 
>> them
>> to bind to the logical action, not the implementation details.)
>>
>> My intuition (and maybe I have the wrong mental model?) is that the
>> pattern matching calling a user written dtor / record accessor is akin
>> to calling a method.  We don't wrap the exceptions thrown by methods
>> apart from some very narrow cases (ie: reflection), and I thought part
>> of reflection's behaviour was related to needing to ensure exceptions
>> (particularly checked ones) were converted to something explicitly
>> handled by the caller.
>>
>> If the dtor / record accessor can declare they throw checked
>> exceptions, then I can kind of see the rationale for wrapping them.
>> Otherwise, it seems clearer to me to let them be thrown without
>> wrapping.
>>
>> I don't think we expect users to explicitly handle MatchException when
>> using pattern matching so what does wrapping gain us here?
>>
>> --Dan
>>


Re: Patterns and GADTs (and type checking and inference and overload selection)

2022-03-30 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, March 30, 2022 9:33:21 PM
> Subject: Patterns and GADTs (and type checking and inference and overload 
> selection)

> GADTs -- sealed families whose permitted subtypes specialize the type
> variables of the base class -- pose some interesting challenges for
> pattern matching.
> 
> (Remi: this is a big, complex area.  Off-the-cuff "this is wrong" or
> "you should X instead" replies are not helpful.  If in doubt, ask
> questions.  One comprehensive reply is more useful than many small
> replies.  Probably best to think about the whole thing for some time
> before responding.)

No disagreement here, it's a nice summary of where we are and what are the 
challenges ahead.

Rémi


Re: [External] : Re: Remainder in pattern matching

2022-03-30 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "Dan Heidinga" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, March 30, 2022 8:26:53 PM
> Subject: Re: [External] : Re: Remainder in pattern matching

[...]

> 
> One thing wrapping gains is that it gives us a place to centralize
> "something failed in pattern matching", which includes exhaustiveness
> failures as well as failures of invariants which PM assumes (e.g., dtors
> don't throw.) 

but such centralization is a bad practice, that the reason why catch(Exception) 
is considered as a bad practice.

BTW, i hope that with loom people will use virtual threads (they are cheap) to 
manage scenarios where you want to discard a computation if something fails, 
like in Erlang.

Rémi


Re: Remainder in pattern matching

2022-03-30 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, March 30, 2022 4:40:28 PM
> Subject: Remainder in pattern matching

> We should have wrapped this up a while ago, so I apologize for the late 
> notice,
> but we really have to wrap up exceptions thrown from pattern contexts (today,
> switch) when an exhaustive context encounters a remainder. I think there's
> really one one sane choice, and the only thing to discuss is the spelling, but
> let's go through it.

> In the beginning, nulls were special in switch. The first thing is to evaluate
> the switch operand; if it is null, switch threw NPE. (I don't think this was
> motivated by any overt null hostility, at least not at first; it came from
> unboxing, where we said "if its a box, unbox it", and the unboxing throws NPE,
> and the same treatment was later added to enums (though that came out in the
> same version) and strings.)

> We have since refined switch so that some switches accept null. But for those
> that don't, I see no other move besides "if the operand is null and there is 
> no
> null handling case, throw NPE." Null will always be a special remainder value
> (when it appears in the remainder.)

> In Java 12, when we did switch expressions, we had to confront the issue of
> novel enum constants. We considered a number of alternatives, and came up with
> throwing ICCE. This was a reasonable choice, though as it turns out is not one
> that scales as well as we had hoped it would at the time. The choice here is
> based on "the view of classfiles at compile time and run time has shifted in 
> an
> incompatible way." ICCE is, as Kevin pointed out, a reliable signal that your
> classpath is borked.

> We now have two precedents from which to extrapolate, but as it turns out,
> neither is really very good for the general remainder case.

> Recall that we have a definition of _exhaustiveness_, which is, at some level,
> deliberately not exhaustive. We know that there are edge cases for which it is
> counterproductive to insist that the user explicitly cover, often for two
> reasons: one is that its annoying to the user (writing cases for things they
> believe should never happen), and the other that it undermines type checking
> (the most common way to do this is a default clause, which can sweep other
> errors under the rug.)

> If we have an exhaustive set of patterns on a type, the set of possible values
> for that type that are not covered by some pattern in the set is called the
> _remainder_. Computing the remainder exactly is hard, but computing an upper
> bound on the remainder is pretty easy. I'll say "x may be in the remainder of
> P* on T" to indicate that we're defining the upper bound.

> - If P* contains a deconstruction pattern P(Q*), null may be in the remainder 
> of
> P*.
> - If T is sealed, instances of a novel subtype of T may be in the remainder of
> P*.
> - If T is an enum, novel enum constants of T may be in the remainder of P*.
> - If R(X x, Y y) is a record, and x is in the remainder of Q* on X, then `R(x,
> any)` may be in the remainder of { R(q) : q in Q*} on R.

> Examples:

> sealed interface X permits X1, X2 { }
> record X1(String s) implements X { }
> record X2(String s) implements X { }

> record R(X x1, X x2) { }

> switch (r) {
> case R(X1(String s), any):
> case R(X2(String s), X1(String s)):
> case R(X2(String s), X2(String s)):
> }

> This switch is exhaustive. Let N be a novel subtype of X. So the remainder
> includes:

> null, R(N, _), R(_, N), R(null, _), R(X2, null)

> It might be tempting to argue (in fact, someone has) that we should try to 
> pick
> a "root cause" (null or novel) and throw that. But I think this is both
> excessive and unworkable.
[...] see below 

> So what I propose is the following simple answer instead:

> - If the switch target is null and no case handles null, throw NPE. (We know
> statically whether any case handles null, so this is easy and similar to what
> we do today.)
> - If the switch is an exhaustive enum switch, and no case handles the target,
> throw ICCE. (Again, we know statically whether the switch is over an enum
> type.)
> - In any other case of an exhaustive switch for which no case handles the
> target, we throw a new exception type, java.lang.MatchException, with an error
> message indicating remainder.
I agree for the first rule, if null is not handled, let throw a NPE. 
For when the static world and the dynamic world disagree, i think your analysis 
has miss an important question, switching on an enum throw an ICCE very late 
when we discover an unknown value, but in the case of a sealed type, 
we can decide to reject the switch much sooner. 

There is a spectrum of choices here, where to throw an ICCE, it can be 
- when the class is verified (when the method is verified for OpenJ9) 
- the first time the switch is reached (with an indy that validates once that 
the different sealed types "permits" set have not changed). 
- when we have exhausted 

Are binding types covariant ? Was: Declared patterns -- translation and reflection

2022-03-30 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, March 29, 2022 11:01:18 PM
> Subject: Declared patterns -- translation and reflection

> Time to take a peek ahead at _declared patterns_. Declared patterns come in
> three varieties -- deconstruction patterns, static patterns, and instance
> patterns (corresponding to constructors, static methods, and instance 
> methods.)
> I'm going to start with deconstruction patterns, but the basic game is the 
> same
> for all three.
Once we have pattern methods, we can have an interface that defines a pattern 
method and a class that implement it, 
something like 

interface I { 
foo() (Object, int); // fake syntax: the first parenthesis are the parameters, 
the seconds are the binding types 
} 

class A implements I { 
foo() (String, int) { ... } 
} 

Do we agree that a binding type can be covariant ? (before saying no, think 
about generics that's the reason we have return type covariance in Java). 

In that case, are we are in trouble with the translation strategy ? 

Rémi 


Re: Declared patterns -- translation and reflection

2022-03-29 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, March 29, 2022 11:01:18 PM
> Subject: Declared patterns -- translation and reflection

> Time to take a peek ahead at _declared patterns_. Declared patterns come in
> three varieties -- deconstruction patterns, static patterns, and instance
> patterns (corresponding to constructors, static methods, and instance 
> methods.)
> I'm going to start with deconstruction patterns, but the basic game is the 
> same
> for all three.

> Ignoring the trivial details, a deconstruction pattern looks like a 
> "constructor
> in reverse":

> ```{.java}
> class Point {
> int x, y;

> Point(int x, int y) {
> this.x = x;
> this.y = y;
> }

[] 

> }
> ```

> Deconstruction patterns share the weird behaviors that constructors have in 
> that
> they are instance members, but are not inherited, and that rather having 
> names,
> they are accessed via the class name.

> Deconstruction patterns differ from static/instance patterns in that they are 
> by
> definition total; they cannot fail to match. (This is a somewhat arbitrary
> simplification in the object model, but a reasonable one.) They also cannot
> have any input parameters, other than the receiver.

> Patterns differ from their ctor/method counterparts in that they have what
> appear to be _two_ argument lists; a parameter list (like ctors and methods),
> and a _binding_ list. The parameter list is often empty (with the receiver as
> the match target). The binding list can be thought of as a "conditional
> multiple return". That they may return multiple values (and, for partial
> patterns, can return no values at all when they don't match) presents a
> challenge for translation to classfiles, and for the reflection model.

>  Translation to methods

> Patterns contain imperative code, so surely we want to translate them to 
> methods
> in some way. The pattern input parameters map cleanly to method parameters.

> The pattern bindings need to tunneled, somehow, through the method return (or
> some other mechanism). For our deconstructor, we might translate as:

> PatternCarrier ()

> (where the method applies the pattern, and PatternCarrier wraps and provides
> access to the bindings) or

> PatternObject ()

> (where PatternObject provides indirection to behavior to invoke the pattern,
> which in turn returns the carrier.)

> With either of these approaches, though, the pattern name is a problem, 
> because
> patterns can be overloaded on their _bindings_, but both of these return types
> are insensitive to bindings.

> It is useful to characterize the "shape" of a pattern with a MethodType, where
> the parameters of the MethodType are the binding types. (The return type is
> less constrained, but it is sometimes useful to use the return type of the
> MethodType for the required type of the pattern.) Call this the "descriptor" 
> of
> the pattern.

> If we do this, we can use some name mangling to encode the descriptor in the
> method name:

> PatternCarrier name$mangle()

> The mangling has to be stable across compilations with respect to any source-
> and binary-compatible changes to the pattern declaration. One mangling that
> works quite well is to use the "symbolic-freedom encoding" of the erasure of
> the pattern descriptor. Because the erasure of the descriptor is exactly as
> stable as any other method signature derived from source declarations, it will
> have the desired binary compatibility properties, overriding will work as
> expected, etc.
I think we need a least to use a special name like  the same way 
we have . 
I agree that we also need to encode the method type descriptor (the carrier 
type) into the name, so the name of the method in the classfile should be 
 or  (or perhaps  ofr 
the pattern methods). 

>  Return value

> In an earlier design, we used a pattern object (which was a bundle of method
> handles) as the return value of the pattern. This enabled clients to invoke
> these via condy and bind method handles into the constant pool for
> deconstruction and static patterns.

> Either way, we make use of some sort of carrier object to carry the bindings
> from the pattern to the client; either we return the carrier from the pattern
> method, or there is a method on the pattern object that we invoke to get a
> carrier. We have a few preferences about the carrier; we'd like to be able to
> late-bind to the actual implementation (i.e., we don't want to freeze the name
> of a carrier class in the method descriptor), and at least for records, we'd
> like to let the record instance itself be the carrier (since it is immutable
> and we can just invoke the accessors to get the bindings.)
So the return type is either Object (too hide the type of the carrier) or a 
lambda that returns an Object (PatternObject or PatternCarrier acting like a 
glorified lambda). 

>  Carriers

> As part of the work on template strings, Jim has put back some code that was
> originally written 

Re: Declared patterns -- translation and reflection

2022-03-29 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, March 29, 2022 11:01:18 PM
> Subject: Declared patterns -- translation and reflection

> Time to take a peek ahead at _declared patterns_. Declared patterns come in
> three varieties -- deconstruction patterns, static patterns, and instance
> patterns (corresponding to constructors, static methods, and instance 
> methods.)
> I'm going to start with deconstruction patterns, but the basic game is the 
> same
> for all three.
I mostly agree with everything said apart from the syntax of a deconstructor 
(see my next message about how small things can be improved). 

I have several problems with the proposed syntax for a deconstructor. 
I can see the appeal of having a code very similar to a constructor but it's a 
trap, a constructor and a deconstructor do not have the same semantics, a 
constructor initialize fields (which have a name) while a deconstructor (or a 
pattern method) initialize bindings which does not have a name at that point 
yet. 

1/ conceptually there is a mismatch, the syntax introduce names for the 
bindings, but they have no names at that point, bindings only have names AFTER 
the pattern matching succeed. 
2/ sending the value of the binding by name is alien to Java. In Java, sending 
values is by the position of the value. 
3/ the conceptual mismatch also exists at runtime, you need to permute the 
value of bindings before creating the carrier because a carrier takes the value 
of the binding by position while the code will takes the value of the bindings 
by name (you need the equivalent of MethodHandles.permuteArguments() otherwise 
you will see the re-organisation of the code if they are side effects). 

Let's try to come with a syntax, 
as i said, bindings have no names at that point so the deconstructor should 
declare the bindings (int, int) and not (int x, int y), 
so a syntax like 

_deconstructor_ (int, int) { 
_send_bindings_(this.x, this.y); 
} 

Here the syntax shows that the value of the bindings are assigned following the 
position of the expression like usual in Java. 

We can discuss if _send_bindings_ should be "return" or another keyword and if 
the binding types should be declared before or after _deconstructor_. 

By example, if you wan to maintain a kind of symmetry with the constructor, we 
can reuse the name of the class instead of _deconstructor_ and move the binding 
types in front of the name of the class to show that the bindings move from the 
class to the pattern matching in the same direction like a return type of a 
method. 
Something like this: 
(int, int) Point { 
_send_bindings_(this.x, this.y); 
} 

To summarize, the proposed syntax does the convey the underlying semantics of 
the bindings initialization and make things more confusing than it should. 

> Ignoring the trivial details, a deconstruction pattern looks like a 
> "constructor
> in reverse":

> ```{.java}
> class Point {
> int x, y;

> Point(int x, int y) {
> this.x = x;
> this.y = y;
> }

> deconstructor(int x, int y) {
> x = this.x;
> y = this.y;
> }
> }
> ```

Rémi 


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)` 

Re: Pattern coverage

2022-03-24 Thread Remi Forax
Thanks for sharing, 
in the text, they are several mentions of the default pattern but the default 
pattern is not defined. 

Rémi 

> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Thursday, March 24, 2022 6:39:21 PM
> Subject: Pattern coverage

> I've put a document at

> [ http://cr.openjdk.java.net/~briangoetz/eg-attachments/Coverage.pdf |
> http://cr.openjdk.java.net/~briangoetz/eg-attachments/Coverage.pdf ]

> which outlines a formal model for pattern coverage, including record patterns
> and the effects of sealing. This refines the work we did earlier. The document
> may be a bit rough so please let me know if you spot any errors. The approach
> here should be more amenable to specification than the previous approach.


Record pattern: matching an empty record

2022-03-13 Thread Remi Forax
Hi all,
while writing the prototype of the runtime,
i found a case i think we never discuss, can we match an empty record ?

record Empty() { }

switch(object) {
  case Empty() -> ...  // no binding here

I think the answer is yes because i don't see why we should do a special case 
for that, but i may be wrong.

Rémi


Record pattern, the runtime side

2022-03-13 Thread Remi Forax
Following the discussions we had, i've implemented a prototype of what can be 
the runtime part of the pattern matching that supports record pattern.

It works in 3 steps:
Step 1, at compile time, the compiler takes all the patterns and creates a tree 
of pattern from the list of patterns,
pattern that starts with the same prefix are merged together.
In the end, the tree of patterns is encoded in the bytecode as a tree of 
constant dynamic (each Pattern is created only from constant and patterns).

  sealed interface Pattern {}
  record NullPattern() implements Pattern {}
  record ConstantPattern(Object constant) implements Pattern {}
  record TypePattern(Class type) implements Pattern {}
  record RecordPattern(Class recordClass, Pattern... patterns) implements 
Pattern {}
  
  record OrPattern(Pattern pattern1, Pattern pattern2) implements Pattern {}
  record ResultPattern(int index, Pattern pattern) implements Pattern {} 

The last two patterns are less obvious, the OrPattern allows to create the tree 
by saying that a pattern should be executed before another one.
Because the patterns are organized as a tree and not a list, we need a way to 
associate which branch of the tree correspond to which pattern from the use 
POV, the ResultPattern encodes in the carrier the index (the first pattern is 
0, the second one is 1, etc) inside the first component of the carrier.

I've chosen to not represent the bindings (and their corresponding the 
component index in the carrier) and to use the fact that binding slot are 
numbered in the order of the tree traversal.
This is maybe too brittle because the compiler and the runtime has to agree on 
the order of the traversal. But this avoids to encode too many information in 
the bytecode.

Step 2, 
at runtime, the first call to invokedynamic with the pattern tree as arguments, 
creates a tree of method handles that will match the pattern.
During that phase, the runtime environment can be checked to see if the pattern 
tree is invalid with the runtime classes, in that case a linkage error is 
thrown.
In the prototype the method is called toMatcher and takes a Lookup (to find the 
accessors of the record of the RecordPattern), a receiverClass the type of the 
variable switch upon,
the carrier type (a method type as a tuple of the type of the binding in the 
tree traversal order), the index of the first binding (in case of a switch the 
first component in the matching index so the binding slots starts at 1) and 
method handle (the null matcher) to call if a null is found (the two possible 
semantics are doNotMatch/return null or throw a NPE).

  pattern.toMatcher(lookup, receiverClass, carrierType, firstBinding (0 or 1), 
nullMatcher);

Step 3,
during the execution,
- if it's an instanceof a carrier which is null means no match otherwise the 
carrier contains the value of the bindings,
- if it's a switch, a carrier which is null means "default" otherwise the 
component 0 or the carrier contains the index of the pattern matched and the 
binding values
- if it's an assignment, the carrier can not be null because the nullMatcher 
throws a NPE earlier, so the carrier contains the binding values

An example of instanceof
https://github.com/forax/switch-carrier/blob/master/src/main/java/com/github/forax/carrier/java/lang/runtime/InstanceOfExamples.java#L15

An example of switch
https://github.com/forax/switch-carrier/blob/master/src/main/java/com/github/forax/carrier/java/lang/runtime/SwitchExamples.java#L17

An example of assignment
https://github.com/forax/switch-carrier/blob/master/src/main/java/com/github/forax/carrier/java/lang/runtime/AssignmentExamples.java#L14

regards,
Rémi


Re: [External] : Re: Proposal: java.lang.runtime.Carrier

2022-03-07 Thread Remi Forax
> From: "Remi Forax" 
> To: "Brian Goetz" 
> Cc: "Jim Laskey" , "amber-spec-experts"
> 
> Sent: Saturday, March 5, 2022 11:54:14 PM
> Subject: Re: [External] : Re: Proposal: java.lang.runtime.Carrier

>> From: "Brian Goetz" 
>> To: "Remi Forax" 
>> Cc: "Jim Laskey" , "amber-spec-experts"
>> 
>> Sent: Friday, March 4, 2022 3:11:44 AM
>> Subject: Re: [External] : Re: Proposal: java.lang.runtime.Carrier

>>>> Either way, we don't need to mutate or replace carriers.
>>> You want the same carrier for the whole pattern matching:

>> I think you're going about this backwards. You seem to have a clear picture 
>> of
>> how pattern matching "should" be translated. If so, you should share! Maybe
>> your way is better. But you keep making statements like "we need" and "we 
>> want"
>> without explaining why.

>>> - if you have a logical OR between patterns (not something in the current 
>>> Java
>>> spec but Python, C# or clojure core.match have it so we may want to add an 
>>> OR
>>> in the future)

>> OR combinators are a good point, but they can be done without a with 
>> operation.

>>> - if different cases starts with the same prefix of patterns, so you don't 
>>> have
>>> to re-execute the de-constructors/pattern methods of the prefix several 
>>> times

>> Agree that optimizing away multiple invocations is good, but again, I don't 
>> see
>> that as being coupled to the pseudo-mutability of the carrier.

>> Perhaps you should start with how you see translation working?
> Sure,
> the idea is that to execute the pattern matching at runtime, each step is
> decomposed into few higher order functions, things like testing, projecting a
> value (deconstructing), etc
> each higher order manipulate one kind of function that takes two values, the
> value we are actually matching and the carrier, and returns a carrier.

> Obviously, each simple function is a method handle, so there is no boxing in 
> the
> middle and everything is inlined.

> Here is a possible decomposition
> - MH of(Object carrier, MH pattern)
> which is equivalent to o -> pattern.apply(o, carrier)
> - MH match(int index)
> which is equivalent to (o, carrier) -> with(index, carrier, 0), i.e. return a
> new carrier with the component 0 updated with index
> - MH do_not_match()
> which is equivalent to match(-1)
> - MH is_instance(Class type)
> which is equivalent to (o, carrier) -> type.isInstance(o)
> - MH is_null()
> which is equivalent to (o, carrier) -> o == null
> - MH throw_NPE(String message)
> which is equivalent to (o, carrier) -> throw new NPE(message)
> - MH project(MH project, MH pattern)
> which is equivalent to (o, carrier) -> pattern.apply(project.apply(o), 
> carrier)
> - MH bind(int binding, MH pattern)
> which is equivalent to (o, carrier) -> pattern.apply(with(o, carrier, binding)
> - MH test(MH test, MH target, MH fallback)
> which is equivalent to (o, carrier) -> test.test(o, carrier)? target.apply(o,
> carrier): fallback.apply(o, carrier)
> - MH or(MH pattern1, MH pattern2)
> which is equivalent to
> (o, carrier) -> {
> var carrier2 = pattern1.apply(o, carrier);
> if (carrier2.accessor[0] == -1) {
> return carrier2;
> }
> return pattern2.apply(o, carrier2);
> }

> For the carrier, the convention is that the component 0 is an int, -1 means 
> "not
> match", and any positive index means the indexth case match.

> In the detail, it's a little more complex because we sometimes need to pass 
> the
> type of the first parameter to correctly type the returned MH and we also need
> an object CarrierMetadata that keep track of the type of the carrier 
> components
> (and provides an empty carrier and the accessors/withers).

> Here is a small example

> record Point( int x, int y) {}
> record Rectangle(Point p1, Point p2) {}

> // Object o = ...
> //switch(o) {
> //  case Rectangle(Point p1, Point p2) -> ...
> //}

> var lookup = MethodHandles. lookup ();

> var carrierMetadata = new CarrierMetadata( methodType (Object. class , int .
> class , Point. class , Point. class ));
> var empty = carrierMetadata.empty();

> var op = of (empty,
> test ( is_instance (Object. class , Rectangle. class ),
> cast (Object. class ,
> or (carrierMetadata,
> project ( record_accessor (lookup, Rectangle. class , 0 ),
> test ( is_null (Point. class ),
> do_not_match (Point. class , carrierMetadata),
> bind ( 1 , carrierMetadata))),
> project ( record_accessor (lookup,

Re: Telling the totality story

2022-03-03 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Thursday, March 3, 2022 9:42:25 PM
> Subject: Telling the totality story

> Given the misconceptions about totality and whether "pattern matching means 
> the
> same thing in all contexts", it is pretty clear that the story we're telling 
> is
> not evoking the right concepts. It is important not only to have the right
> answer, but also to have the story that helps people understand why its right,
> so let's take another whack at that.

> (The irony here is that it only makes a difference at one point -- null -- 
> which
> is the least interesting part of the story. So it is much sound and fury that
> signifies nothing.)

> None of what we say below represents a change in the language; it is just
> test-driving a new way to tell the same story. (From a mathematical and
> specification perspective, it is a much worse story, since it is full of
> accidental detail about nulls (among other sins), but it might make some 
> people
> happier, and maybe we can learn something from the reaction.)

> The key change here is to say that every pattern-matching construct needs
> special rules for null. This is already true, so we're just doubling down on
> it. None of this changes anything about how it works.

[...] 

>  Let

> This is where it gets ugly. Let has no opinions about null, but the game here 
> is
> to pretend it does. So in a let statement:

> let P = e

> - Evaluate e
> - If e is null
> - If P is a covering pattern, its binding is bound to null;
> - else we throws NPE
> - Otherwise, e is matched to P. If it does not match (its remainder), a
> MatchException is thrown (or the else clause is taken, if one is present.)
It's not clear to me why a MatchException should be thrown instead of not 
compiling if not exhaustive. 
It's mean that there are remainders that does not lead to either a NPE or an 
error, do you have an example of such remainder ? 

Rémi 


Re: Proposal: java.lang.runtime.Carrier

2022-03-03 Thread Remi Forax
For the pattern matching, 
we also need a 'with' method, that return a method handle that takes a carrier 
and a value and return a new carrier with the component value updated. 

static MethodHandle withComponent(MethodType methodType, int i) 
// returns a mh (Carrier;T) -> Carrier with T the type of the component 

It can be built on top of constructor() + component() but i think that i should 
be part of the API instead of every user of the Carrier API trying to 
re-implement it. 

In term of spec, Jim, can you rename "component getter" to "component accessor" 
which is the term used by records. 

Rémi 

> From: "Brian Goetz" 
> To: "Jim Laskey" , "amber-spec-experts"
> 
> Sent: Thursday, March 3, 2022 4:29:51 PM
> Subject: Re: Proposal: java.lang.runtime.Carrier

> Thanks Jim.

> As background, (some form of) this code originated in a prototype for pattern
> matching, where we needed a carrier for a tuple (T, U, V) to carry the results
> of a match from a deconstruction pattern (or other declared pattern) on the
> stack as a return value. We didn't want to spin a custom class per pattern, 
> and
> we didn't want to commit to the actual layout, because we wanted to preserve
> the ability to switch later to a value class. So the idea is you describe the
> carrier you want as a MethodType, and there's a condy that gives you an MH 
> that
> maps that shape of arguments to an opaque carrier (the constructor), and other
> condys that give you MHs that map from the carrier to the individual bindings.
> So pattern matching will stick those MHs in CP slots.

> The carrier might be some bespoke thing (e.g., record anon(T t, U u, V v)), or
> something that holds an Object[], or something with three int fields and two
> ref fields, or whatever the runtime decides to serve up.

> The template mechanism wants almost exactly the same thing for bundling the
> parameters for uninterprted template strings.

> Think of it as a macro-box; instead of boxing primitives to Object and Objects
> to varargs, there's a single boxing operation from a tuple to an opaque type.

> On 3/3/2022 8:57 AM, Jim Laskey wrote:

>> We propose to provide a runtime anonymous carrier class object generator ;
>> java.lang.runtime.Carrier . This generator class is designed to share 
>> anonymous
>> classes when shapes are similar. For example, if several clients require
>> objects containing two integer fields, then Carrier will ensure that each
>> client generates carrier objects using the same underlying anonymous class.

>> Providing this mechanism decouples the strategy for carrier class generation
>> from the client facility. One could implement one class per shape; one class
>> for all shapes (with an Object[]), or something in the middle; having this
>> decision behind a bootstrap means that it can be evolved at runtime, and
>> optimized differently for different situations. Motivation

>> The [ https://bugs.openjdk.java.net/browse/JDK-8273943 | String Templates JEP
>> draft ] proposes the introduction of a TemplatedString object for the primary
>> purpose of carrying the template and associated values derived from a 
>> template
>> literal . To avoid value boxing, early prototypes described these carrier
>> objects using per-callsite anonymous classes shaped by value types, The use 
>> of
>> distinct anonymous classes here is overkill, especially considering that many
>> of these classes are similar; containing one or two object fields and/or one 
>> or
>> two integral fields. Pattern matching has a similar issue when carrying the
>> values for the holes of a pattern. With potentially hundreds (thousands?) of
>> template literals or patterns per application, we need to find an alternate
>> approach for these value carriers . Description

>> In general terms, the Carrier class simply caches anonymous classes keyed on
>> shape. To further increase similarity in shape, the ordering of value types 
>> is
>> handled by the API and not in the underlying anonymous class. If one client
>> requires an object with one object value and one integer value and a second
>> client requires an object with one integer value and one object value, then
>> both clients will use the same underlying anonymous class. Further, types are
>> folded as either integer (byte, short, int, boolean, char, float), long 
>> (long,
>> double) or object. [We've seen that performance hit by folding the long group
>> into the integer group is significant, hence the separate group.]

>> The Carrier API uses MethodType parameter types to describe the shape of a
>> carrier. This incorporates with the primary use case where bootstrap methods
>> need to capture indy non-static arguments. The API has three static methods;
>> // Return a constructor MethodHandle for a carrier with components
>> // aligning with the parameter types of the supplied methodType.
>> static MethodHandle constructor(MethodType methodType)

>> // Return a component getter MethodHandle for component i.
>> 

Re: Primitive type patterns

2022-02-28 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Monday, February 28, 2022 5:50:25 PM
> Subject: Re: Primitive type patterns

> Let me put the central question here in the spotlight.

>>  Boxing and unboxing

>> Suppose our match target is a box type, such as:

>> record IntegerBox(Integer i) { }

>> Clearly we can match it with:

>> case IntegerBox(Integer i):
> We could stop here, and say the pattern `int i` is not applicable to 
> `Integer`,
> which means that `IntegerBox(int i)` is not applicable to IntegerBox. This is 
> a
> legitimate choice (though, I think it would be a bad one, one that we would
> surely revisit sooner rather than later.)

> Users might well not understand why they could not say

> case IntegerBox(int i)

> because (a) you can unbox in other contexts and (b) this has a very clear
> meaning, and one analogous to `case ObjectBox(String s)` -- if the box 
> contents
> fits in a String, then match, otherwise don't. They might get even more balky
> if you could say

> int x = anInteger

> but not

> let int x = anInteger

> And, if we went with both Remi's preferences -- the minimalist matching 
> proposal
> and dropping "let" in the let/bind syntax (which we will NOT discuss further
> here) then the following would be ambiguous:

> int x = anInteger // is this an assignment, in which case we unbox, or a 
> match,
> in which case we don't?

> and we would have to "arbitrarily" pick the legacy interpretation.
I said the opposite, if you use "let" you do not have to support assignment 
conversions. 

> But all of this "balking" is symptom, not disease.

> The reality is that pattern matching is more primitive than unboxing
> conversions, and if we lean into that, things get simpler. An unboxing
> conversion may seem like one thing, but is actually two: try to match against 
> a
> partial pattern, and if there is no match, fail. In other words, unboxing is:

> int unbox(Integer n) ->
> switch(n) {
> case int i -> i;
> case null -> throw new NPE();
> }

> The unboxing we have jams together the pattern match and the throw-on-fail,
> because it has no choice; unboxing wants to be total, and there's no place to
> specify what to try if the pattern match fails. But pattern matching is
> inherently conditional, allowing us to build different constructs on it which
> handle failures differently.

> So *of course* there's an obvious definition of how `int x` matches against
> Integer, and its not a question of whether we "define" it that way, its a
> question of whether we expose the obvious meaning, or suppress it. I think the
> arguments in favor of suppression are pretty weak.
The strong argument is that instanceof/switch case is about subtyping 
relationship while assignment is about assignment conversions, trying to blur 
the lines between the two has already been tried in the past and it results in 
pain (see below). But i suppose every generations need to re-discover it. 

> There's a similar argument when it comes to narrowing from (say) long to int.
> There's a very natural interpretation of matching `int x` to a long: does the
> long value fit in an int. In assignment context, we do the best we currently
> can -- we allow the narrowing only if we can statically prove it will match,
> which mean if the match target is a constant. But again, conversions in
> assignment context are not the primitive. If we take the obvious definition of
> matching `int x` against long, then the current rules fall out naturally, and
> we can ask sensible questions like

> if (aLong instanceof byte) { ... }
> else if (aLong instanceof short) { ... }

> by *asking the primitive directly*, rather than appealing to some proxy (like
> manually unrolling the range check.)
What about ? 

Object o =... 
if (o instanceof byte) { ... } 

Does it means that o can be a Long ? 

Rémi 


Re: Primitive type patterns

2022-02-26 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Friday, February 25, 2022 10:45:44 PM
> Subject: Primitive type patterns

> As a consequence of doing record patterns, we also grapple with primitive type
> patterns. Until now, we've only supported reference type patterns, which are
> simple:

> - A reference type pattern `T t` is applicable to a match target of type M if 
> M
> can be cast to T without an unchecked warning.

> - A reference type pattern `T t` covers a match type M iff M <: T

> - A reference type pattern `T t` matches a value m of type M if M <: T || m
> instanceof T

> Two of these three characterizations are static computations (applicability 
> and
> coverage); the third is a runtime test (matching). For each kind of pattern, 
> we
> have to define all three of these.

>  Primitive type patterns in records

> Record patterns necessitate the ability to write type patterns for any type 
> that
> can be a record component. If we have:

> record IntBox(int i) { }

> then we want to be able to write:

> case IntBox(int i):

> which means we need to be able to express type patterns for primitive types.

>  Relationship with assignment context

> There is another constraint on primitive type patterns: the let/bind statement
> coming down the road. Because a type pattern looks (not accidentally) like a
> local variable declaration, a let/bind we will want to align the semantics of
> "local variable declaration with initializer" and "let/bind with total type
> pattern". Concretely:

> let String s = "foo";

> is a pattern match against the (total) pattern `String s`, which introduces 
> `s`
> into the remainder of the block. Since let/bind is a generalization of local
> variable declaration with initialization, let/bind should align with locals
> where the two can express the same thing. This means that the set of
> conversions allowed in assignment context (JLS 5.2) should also be supported 
> by
> type patterns.

> Of the conversions supported by 5.2, the only one that applies when both the
> initializer and local variable are of reference type is "widening reference",
> which the above match semantics (`T t` matches `m` when `M <: T`) support. So
> we need to fill in the other three boxes of the 2x2 matrix of { ref, primitive
> } x { ref, primitive }.
That's a huge leap, let's take a step back. 

I see two questions that should be answered first. 
1) do we really want pattern in case of assignment/declaration to support 
assignment conversions ? 
2) do we want patterns used by the switch or instanceof to follow the exact 
same rules as patterns used in assignment/declaration ? 

For 1, given that we are using pattern to do destructured assignment, we may 
want to simplify the assignment rules to keep things simple avoid users 
shooting themselves in the foot with implicit unboxing. 
With an example, 
record Box(T value) {} 
Box box = ... 
Box<>(int result) = box; // assignment of result may throw a NPE 

I don't think we have to support that implicit unboxing given that we have a 
way to ask for an unboxing explicitly (once java.lang.Integer have a 
de-constructor) 

Box<>(Integer(int result)) = box; 

I think we should not jump with the shark too soon here and ask ourselves if we 
really want assignment conversions in case of destructured assignment. 

2) we already know that depending on the context (inside a switch, inside a 
instanceof, inside an assignment) the rules for pattern are not exactly the 
same. 
So we may consider that in the assignment context, assignment conversions apply 
while for a matching context, simpler rules apply. 
Given that the syntax for switch reuse '->', i believe we should use the 
overriding rules (the one we use for lambdas) instead of the assignment rules 
(the one we use for method reference). 
And yes, i know that the '->' of switch is not the same as the '->' of lambda, 
but i don't think we should bother users to intuitively think that the same 
rules apply. 

Then the model you propose is too clever for me, the fact that 
instanceof Point(double x, double y) 
has a different meaning depending if Point is declared like 
record Point(double x, double y) { } 
or like this 
record Point(Double x, Double y) { } 
is too much. 

The semantics of Java around null is already a giant landmine field, we should 
restraint ourselves to add more null-funny behaviors. 

regards, 
Rémi 


Re: Record patterns (and beyond): exceptions

2022-02-17 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 7:34:17 PM
> Subject: Record patterns (and beyond): exceptions

> As we move towards the next deliverable -- record patterns -- we have two new
> questions regarding exceptions to answer.

>  Questions

> 1. When a dtor throws an exception. ( You might think we can kick this down 
> the
> road, since records automatically acquire a synthetic dtor, and users can't
> write dtors yet, but since that synthetic dtor will invoke record component
> accessors, and users can override record component accessors and therefore 
> they
> can throw, we actually need to deal with this now.)

> This has two sub-questions:

> 1a. Do we want to do any extra type checking on the bodies of dtors / record
> accessors to discourage explicitly throwing exceptions? Obviously we cannot
> prevent exceptions like NPEs arising out of dereference, but we could warn on
> an explicit throw statement in a record accessor / dtor declaration, to remind
> users that throwing from dtors is not the droid they are looking for.
For de-constructor, given that they does not exist yet, we can do like record 
constructor, banned checked exceptions and for accessors, we can emit a warning 
as you suggest and do not allow record pattern if one of the getters throws a 
checked exception. 

> 1b. When the dtor for Foo in the switch statement below throws E:

> switch (x) {
> case Box(Foo(var a)): ...
> case Box(Bar(var b)): ...
> }

> what should happen? Candidates include:

> - allow the switch to complete abruptly by throwing E?
> - same, but wrap E in some sort of ExceptionInMatcherException?
> - ignore the exception and treat the match as having failed, and move on to 
> the
> next case?
The nice thing about the rules above is that a record pattern can never throw a 
checked exception. So there is nothing to do here. 

> 2. Exceptions for remainder. We've established that there is a difference
> between an _exhaustive_ set of patterns (one good enough to satisfy the
> compiler that the switch is complete enough) and a _total_ set of patterns 
> (one
> that actually covers all input values.) The difference is called the
> _remainder_. For constructs that require totality, such as pattern switches 
> and
> let/bind, we have invariants about what will have happened if the construct
> completes normally; for switches, this means exactly one of the cases was
> selected. The compiler must make up the difference by inserting a throwing
> catch-all, as we already do with expression switches over enums, and all
> switches over sealed types, that lack total/default clauses.

> So far, remainder is restricted to two kinds of values: null (about which 
> switch
> already has a strong opinion) and "novel" enum constants / novel subtypes of
> sealed types. For the former, we throw NPE; for the latter, we throw ICCE.

> As we look ahead to record patterns, there is a new kind of remainder: the
> "spine" of nested record patterns. This includes things like Box(null),
> Box(novel), Box(Bag(null)), Box(Mapping(null, novel)), etc. It should be clear
> that there is no clean extrapolation from what we currently do, to what we
> should do in these cases. But that's OK; both of the existing
> remainder-rejection cases derive from "what does the context think" -- switch
> hates null (so, NPE), and enum switches are a thing (which makes ICCE on an
> enum switch reasonable.) But in the general case, we'll want some sort of
> MatchRemainderException.
Nope, it can not be a runtime exception because people will write code to catch 
it and we will have a boat load of subtle bugs because exception are side 
effect so you can see in which order the de-constructors or the pattern methods 
are called. ICCE is fine. 

> Note that throwing an exception from remainder is delayed until the last
> possible moment. We could have:

> case Box(Bag(var x)): ...
> case Box(var x) when x == null: ...

> and the reasonable treatment is to treat Box(Bag(var x)) as not matching
> Box(null), even though it is exhuastive on Box>), and therefore fall
> into the second case on Box(null). Only when we reach the end of the switch,
> and we haven't matched any cases, do we throw MatchRemainderException.
I really dislike that idea, it will be a burden in the future each time we want 
to change the implementation. 
I would like the semantics to make no promise about when the error will be 
thrown, the semantics should not be defined if a deconstructors/pattern method 
does a side effect, the same way the stream semantics is not defined if the 
lambda taken as parameter of Stream.map() does a side effect. 
I think the parallel between the pattern matching and a stream in term of 
execution semantics is important here. From the outside, those things are 
monads, they should work the same way. 

Rémi 


Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 4:49:19 PM
> Subject: Re: Reviewing feedback on patterns in switch

> One thing that we have, perhaps surprisingly, *not* gotten feedback on is
> forcing all non-legacy switches (legacy type, legacy labels, statement only) 
> to
> be exhaustive. I would have thought people would complain about pattern
> switches needing to be exhaustive, but no one has! So either no one has tried
> it, or we got away with it...
Yes, we had several feedbacks about the opposite, why the switch statement on 
an enum is not exhaustive, i.e. why the following code does not compile 

enum Color { RED , BLUE } 
int x; 
Color color = null ; 
switch (color) { 
case RED -> x = 0 ; 
case BLUE -> x = 1 ; 
} 
System. out .println(x);  // x may not be initialized 
Rémi 

> On 1/25/2022 2:46 PM, Brian Goetz wrote:

>> We’ve previewed patterns in switch for two rounds, and have received some
>> feedback.  Overall, things work quite well, but there were a few items which
>> received some nontrivial feedback, and I’m prepared to suggest some changes
>> based on them.  I’ll summarize them here and create a new thread for each 
>> with
>> a more detailed description.

>> I’ll make a call for additional items a little later; for now, let’s focus on
>> these items before adding new things (or reopening old ones.)

>> 1.  Treatment of total patterns in switch / instanceof

>> 2.  Positioning of guards

>> 3.  Type refinements for GADTs

>> 4.  Diamond for type patterns (and record patterns)


Re: Reviewing feedback on patterns in switch

2022-02-15 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, February 15, 2022 7:50:06 PM
> Subject: Re: Reviewing feedback on patterns in switch

> We're preparing a third preview of type patterns in switch. Normally we would
> release after a second preview, but (a) we're about to get record patterns,
> which may disclose additional issues with switch, so best to keep it open for
> at least another round, and (b) we're proposing some nontrivial changes which
> deserve another preview.

> Here's where we are on these.

>> 1.  Treatment of total patterns in switch / instanceof

> Quite honestly, in hindsight, I don't know why we didn't see this sooner; the
> incremental evolution proposed here is more principled than where we were in
> the previous round; now the construct (instanceof, switch, etc) *always* gets
> first crack at enforcing its nullity (and exception) opinions, and *then*
> delegates to the matching semantics of the pattern if it decides to do so. 
> This
> fully separates pattern semantics from conditional construct semantics, rather
> than complecting them (which in turn deprived users of seeing the model more
> clearly.) In hindsight, this is a no-brainer (which is why we preview things.)
> We'll be addressing this in the 3rd preview.
Not sure it's a no-brainer. 
The question is more a question of consistency. There are two consistencies and 
we have to choose one, either switch never allows null by default and users 
have to opt-in with case null or we want patterns to behave the same way if 
they are declared at top-level or if they are nested. I would say that the 
semantics you propose is more like the current Java and the other semantics is 
more like the Java of a future (if we choose the second option). 

I think we should try the semantics you propose and see if people agree or not. 

>> 2.  Positioning of guards

> Making guards part of switch also feels like a better factoring than making 
> them
> part of patterns; it simplifies patterns and totality, and puts switch on a
> more equal footing with our other conditional constructs. We did go back and
> forth a few times on this, but having given this a few weeks to settle, I'm
> pretty convinced we'd regret going the other way.

> There were two sub-points here: (a) is the guard part of the pattern or part 
> of
> switch, and (b) the syntax. There was general agreement on (a), but some had
> preference for && on (b). I spent some more time thinking about this choice,
> and have come down firmly on the `when` side of the house as a result for a
> number of reasons.
Still agree on (a) 

> - Possibility for ambiguity. If switching over booleans (which we will surely
> eventually be forced into), locutions like `case false && false` will be very
> confusing; it's pure puzzler territory.
> - && has a stronger precedence than keyword-based operators like 
> `instanceof`';
> we want guards to be weakest here.
I don't understand your point, we want instanceof pattern && expression to be 
equivalent to instanceof type && expression + cast, so the fact that && has a 
stronger precedence makes that possible so it's not an issue. 

> - Using && will confuse users about whether it is part of the expression, or
> part of the switch statement. If we're deciding it is part of the switch, this
> should be clear, and a `when` clause makes that clear.
I don't think it's that important, apart if we start to also want to combine 
patterns with && 

> - There are future constructs that may take patterns, and may (or may not) 
> want
> to express guard-like behavior, such as `let` statements (e.g., let .. when ..
> else.) Expressing guards here with && is even less evocative of "guard
> condition" than it is with switches.
It's not clear to me how to use "let when else". Is it more like a ?: in C than 
the let in in Caml ? 

> - Users coming from other languages will find `case...when` quite clear.
> - We've talked about "targetless" switches as a possible future feature, which
> express multi-way conditionals:

> switch {
> case when (today() == TUESDAY): ...
> case when (location() == GREENLAND): ...
> ...
> }

> This would look quite silly with &&.
For me, this is like cond in Lisp but more verbose. using "case" and "when" 
here is sillly. 

> Similarly, one could mix guards with a targeted switch:

> switch (x) {
> case Time t: ...
> case Place p: ...
> default when (today() == TUESDAY): ... tuesday-specific default
> default: ... regular default ...
default && today() == TUESDAY is fine for me. 

> Expressing guards that are the whole condition with `when` is much more 
> natural
> than with &&.
For me, && is more natural than "when" because i've written more switch that 
uses && than "when". 
And don't forget that unlike most of the code, with pattern matching the number 
of characters does matter, this is more similar to lambdas, if what you write 
is too verbose, you will not write it. 

> tl;dr: inventing a `when` modifier on switch 

Re: JEP 405 update

2022-02-09 Thread Remi Forax
Hi Gavin,
I don't buy the argument that record patterns promote a null-safe style of 
programming as this is stated several times in the JEP.

The ""null-safety"" (notice the air quotes) mostly comes from the instanceof or 
the switch (which semantics is equivalent of a cascade of if instanceof), not 
from the record pattern by itself.

You can argue that when a record pattern is nested a nullcheck appears, but 
it's more than the underlying semantics is a degenerated instanceof when the 
declared type and the instanceof type are the same.

The record pattern is about destructuring after the instanceof/nullcheck has 
been done, so i find that argument counter productive because it does not help 
to understand the semantics.

Also, we have talked several times to introduce the record pattern when doing 
an assignment
  Point point = ...
  Point(int x, int y) = point;
  // can use x and y here !

This will throw a NPE if point is null, similarly to an unboxing operation.

The null-safety is not attached to the record pattern per se but by the 
container that use it (instanceof, case of a switch, enclosing pattern).

regards,
Rémi

- Original Message -
> From: "Gavin Bierman" 
> To: "amber-spec-experts" 
> Sent: Wednesday, February 9, 2022 12:59:02 PM
> Subject: JEP 405 update

> Dear experts,
> 
> Just to let you know that I have updated JEP 405:
> 
> https://openjdk.java.net/jeps/405
> 
> You will see that we have removed the array patterns from this JEP (and it has
> been retitled accordingly). We're still committed to supporting a direct 
> pattern
> form for arrays, but given our tight schedule and a number of queries about 
> the
> exact syntactic form for array patterns, we think we'll be better off 
> decoupling
> them from JEP 405, and releasing them in a future patterns JEP.
> 
> Comments welcomed!
> Gavin


Re: Control flow analysis for exhaustive pattern-switch statement

2022-02-04 Thread Remi Forax
> From: "Brian Goetz" 
> To: "Tagir Valeev" , "amber-spec-experts"
> 
> Sent: Thursday, February 3, 2022 5:35:13 PM
> Subject: Re: Control flow analysis for exhaustive pattern-switch statement

> Thanks Tagir for digging into this. This is good analysis.

> The decision to require completeness of enhanced switches was taken relatively
> late, so its quite possible we have left something out. And in the next round,
> when nested patterns come along, we need to formalize totality and remainder,
> which also intersects with this problem. So this is timely.

[...] 

>> I understand that the same reasoning does not apply for switch over
>> enums, as for compatibility reasons, default behavior is to do
>> nothing. However, for patterns, uninitialized `x` cannot appear after
>> the switch, even if we recompile the sealed interface separately
>> adding one more inheritor.

> Slight correction: *statement* switches over enums. We've carved out a place
> where the only switches that are not total, are *statement* switches over the
> legacy types with the legacy switch labels. In that case, DU analysis picks up
> some of the slack.

> (It is still on our "to be considered" list whether it is worth it to allow
> statement switches to be explicitly marked as total to engage greater
> typechecking, or whether we want to embark on the decade-long path of warning
> increasingly loudly on non-total switches until we eventually make them
> illegal.)
Both options are not exclusive, we can have a switch marked as exhaustive AND 
have a warning to nudge people to the "right" path. 

I believe we should mark a way to mark switch as total but i would prefer to 
have a syntax which is not something like total-switch which to me is a 
regression given that we have decided to rehabilitate the switch instead of 
using another keyword (what's Brian called the snitch approach). 

If we try to summarize the approachs we have discussed, we have 
- use a new keyword like "total-switch" 
- use a new keyword inside the parenthesis of the switch, switch(total value) { 
... } 
- use default -> throw new AnException() 
- use default -> throw; as a shortcut 

The two later propositions are not good because adding a "default" make the 
switch exhaustive but we are lose the typechecking when a new enum constant is 
introduced. 
The two former propositions are not good because it's a snitch in disguise. 

I believe a good compromise might be to have a syntax inside the curly braces 
like "default -> " but not using "default" because it's not the semantics we 
want. 
It just occurs to me that we can use an ellipsis (...) for that 
switch(option) { 
case READ -> { } 
case WRITE -> { } 
case READ_WRITE -> { } 
... // this is a real syntax, it means that the switch is total 
} 

An ellipsis is an opt-in way to ask for an exhaustive checking, it will fail at 
compile time if one of the constants is not listed, and in case of separate 
compilation at runtime an error will be thrown. 

[...] 

> Thanks,
> -Brian

Rémi 


Re: Reviewing feedback on patterns in switch

2022-01-28 Thread Remi Forax
- Original Message -
> From: "mark" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Friday, January 28, 2022 12:43:55 PM
> Subject: Re: Reviewing feedback on patterns in switch

> On 2022-01-25T19:46:09 +
> Brian Goetz  wrote:
> 
>> We’ve previewed patterns in switch for two rounds, and have received some
>> feedback.  Overall, things work quite well, but there were a few items which
>> received some nontrivial feedback, and I’m prepared to suggest some changes
>> based on them.  I’ll summarize them here and create a new thread for each 
>> with
>> a more detailed description.
>> 
>> I’ll make a call for additional items a little later; for now, let’s focus on
>> these items before adding new things (or reopening old ones.)
>> 
>> 1.  Treatment of total patterns in switch / instanceof
>> 
>> 2.  Positioning of guards
>> 
>> 3.  Type refinements for GADTs
>> 
>> 4.  Diamond for type patterns (and record patterns)
> 
> Hello!
> 
> I'm a little late to the party, as ever, but is there a specific build I
> should be looking at so that I can get a better idea of what the current
> state of things are?

Hi Mark,
the last jdk18 build or any new jdk19 builds will do.

> 
> --
> Mark Raynsford | https://www.io7m.com


Rémi


Feedback: refining the inference of yield type in a switch

2022-01-26 Thread Remi Forax
Here are several examples showing that the inference does work as expected from 
a beginner POV,
the first two examples come from one of my student.
 
Here, list is typed as List instead of List
  var value = ...
  var list = switch (value) {
case 0 -> List.of();
case 1 -> List.of("foo");
default -> throw new AssertionError();
  };

if we add a cast in front of to try to help inference
  var list = (List) switch (value) {
case 0 -> List.of();
case 1 -> List.of("foo");
default -> throw new AssertionError();
  };
the inference does not care, so it becomes an unsafe cast.

Declaring the type explicitly works
  List list = switch (value) {
case 0 -> List.of();
case 1 -> List.of("foo");
default -> throw new AssertionError();
  };

Specifying the type argument for List.of() also works.
  var list = switch (value) {
case 0 -> List.of();
case 1 -> List.of("foo");
default -> throw new AssertionError();
  };

To summarize, the inference considers each yield type one by one instead of all 
together (each one adding it's own constraints).
I think we should refine the way the inference work so "it just works" in this 
kind of case.

regards,
Rémi


Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 4:50:05 AM
> Subject: Re: Positioning of guards (was: Reviewing feedback on patterns in 
> switch)

> Hello!
> 
> For the record: I like the current version with &&. It's short and
> easy to understand (as people already know what && means in Java). I
> see no reason in replacing it with `when`, which is more limiting.
> 
>> because of the potential confusion should we ever choose to support switch 
>> over
>> boolean
> 
> It looks like any boolean expression that is a potentially constant
> differs syntactically from the guarded pattern, so we can distinguish
> between guarded pattern with && and boolean expression on AST level
> without resolving the references. End users will unlikely use anything
> other than explicit 'true' and 'false' constants, so it will add some
> complexity to the compiler but does not add any problems to real users
> 
>> because the && did not stand out enough as turning a total pattern into a
>> partial one
> 
> I think it's a matter of taste and habit. I, for one, already get used
> to it. It signals about partiality much more, compared to a simple
> type pattern. Looking at `CharSequence cs`, you cannot say whether
> it's total or not if you don't know the type of the selector
> expression. However, looking at `CharSequence cs && cs.length() > 0`
> you are completely sure it's not total. So if we need a clear signal
> to tell total and partial patterns apart, we have much bigger problems
> with type patterns.
> 
>> Guarded patterns are never total
> Except when guard is a constant expression that evaluates to `true`:
> 
> void test(Object obj) {
>switch (obj) { // compiles
>case Object s && true -> System.out.println(s);
>}
> }

I think we should separate the two ideas in Brian's mail,
one is should we allow a guard inside a pattern ? and the other is what is the 
syntax for a guard ?

My position is that we should only allow guard in a switch, not as pattern.

And i see no problem to use "&&" instead of "when", as Tagir, i'm kind of used 
to it too.

regards,
Rémi

> 
> On Wed, Jan 26, 2022 at 2:49 AM Brian Goetz  wrote:
>>
>> > 2.  Positioning of guards
>>
>> We received several forms of feedback over the form and placement of guarded
>> patterns.  Recall that we define a guarded pattern to be `P && g`, where P 
>> is a
>> pattern and g is a boolean expression.  Guarded patterns are never total.  
>> Note
>> that we had a choice of the guard being part of the pattern, or being part of
>> the `case` label; the current status chooses the former.  (Part of our
>> reasoning was that there might be other partial pattern contexts coming, and 
>> we
>> didn’t want to solve this problem each time. (For instanceof, it makes no
>> difference.) )
>>
>> I am prepared to reconsider the association of the guard with the pattern, 
>> and
>> instead treat it as part of the case.  This is expressively weaker but may 
>> have
>> other advantages.
>>
>> Additionally, people objected to the use of &&, not necessarily because
>> “keywords are better”, but because of the potential confusion should we ever
>> choose to support switch over boolean, and because the && did not stand out
>> enough as turning a total pattern into a partial one.  What the alternative
>> looks like is something like:
>>
>> switch (x) {
>> case Foo(var x, var y)
>> when x == y -> A;
>> case Foo(var x, var y) -> B;
>> }
>>
>> Here, `when` (bike shed to be painted separately) is a qualifier on the case,
>> not the pattern.  A total pattern with a `when` is considered a partial case.
>> This simplifies patterns, and moves the complexity of guards into switch,
>> where arguably it belongs.
>>
>> The loss of expressiveness is in not allowing nested patterns like:
>>
>> P(Q && guard)
>>
>> and instead having to move the guard to after the matching construct.  Some
>> users recoiled at seeing guards inside pattern invocations; it seemed to some
>> like mixing two things that should stay separate.  (For unrolling a nested
>> pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when
> > alpha instanceof Q`.)


Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 5:20:24 AM
> Subject: Re: Treatment of total patterns (was: Reviewing feedback on patterns 
> in switch)

>> Null is only matched by a switch case that includes `case null`.  Switches 
>> with
>> no `case null` are treated as if they have a `case null: throw NPE`.  This
>> means that `case Object o` doesn’t match null; only `case null, Object o` 
>> does.
>> Total patterns are re-allowed in instanceof expressions, and are consistent 
>> with
>> their legacy form.
> 
> I strongly support this change.
> 
> In my experience, it's much more important to have automatic
> refactorings between switch and chains of 'if' than between nested and
> flat switches. People have chains of 'if's very often and they are not
> legacy. Sometimes, you want to add conditions unrelated to the
> selector expression, so it could be natural to convert 'switch' to
> 'if'. In other cases, you simplify the chain of 'if' statements and
> see that the new set of conditions nicely fits into a pattern switch.
> These if<->switch conversions will be an everyday tool for developers.
> In contrast, destructuring with a switch will be a comparatively rare
> thing, and it's even more rare when you need to convert nested
> switches to flat ones or vice versa. I'm saying this from my Kotlin
> programming experience where you can have when-is and sort of
> destructuring of data classes which are roughly similar to what we are
> doing for Java. One level 'when' is common, two-level 'when' or
> conditions on destructuring components are more rare.
> 
> We already implemented some kind of switch<->if conversion in IntelliJ
> IDEA. And it already has a number of corner cases to handle in order
> to support total patterns that match null. In particular, we cannot
> convert `case Object obj` to `if (x instanceof Object obj), as total
> patterns are prohibited for instanceof and null won't be matched
> anyway. We cannot just omit a condition, as `obj` could be used
> afterwards, so we have to explicitly declare a variable (and I
> believe, this part is still buggy and may produce incompilable code).
> The proposed change will make switch<->if refactorings more mechanical
> and predictable.
> 
> Another thing I mentioned before and want to stress again is that this
> change will allow us to infer required nullity for the variable used
> in the switch selector from AST only. No need to use resolution or
> type inference. This will make interprocedural analysis stronger.
> E.g., consider:
> // Test.java
> class Test {
>  static void test(A a) {
>switch(a) {
>case B b -> {}
>case C c -> {}
>case D d -> {}
>}
>  }
> }
> 
> There are two possibilities:
> 1. D is a superclass of A, thus the last pattern is total, and null is
> accepted here:
> 
> interface D {}
> interface A extends D {}
> interface B extends A {}
> interface C extends A {}
> 
> 2. A is a sealed type with B, C, and D inheritors, switch is
> exhaustive, and null is not accepted here:
> 
> sealed interface A {}
> non-sealed interface B extends A {}
> non-sealed interface C extends A {}
> non-sealed interface D extends A {}
> 
> So without looking at A definition (which might be anywhere) we cannot
> say whether test(null) will throw NPE or not. We cannot cache the
> knowledge about 'test' method parameter nullability within the
> Test.java, because its nullability might change if we change the
> hierarchy of A, even if Test.java content is untouched. Currently, we
> are conservative and not infer nullability when any unguarded pattern
> appears in switch cases. With the required `case null`, we can perform
> more precise analysis.


We should go a step further, this also means that with

switch(box) {
   case Box(B b) -> {}
   case Box(C c) -> {}
   case Box(D d) -> {}
   }

we have no idea if the switch will accept Box(null) or not.

So the idea that a type behave differently if nested inside a pattern or not is 
not a good one.

> 
> With best regards,
> Tagir Valeev.

regards,
Rémi

> 
> On Wed, Jan 26, 2022 at 2:47 AM Brian Goetz  wrote:
>>
>>
>> 1.  Treatment of total patterns in switch / instanceof
>>
>>
>> The handling of totality has been a long and painful discussion, trying to
>> balance between where we want this feature to land in the long term, and
>> people’s existing mental models of what switch and instanceof are supposed to
>> do.  Because we’ve made the conscious decision to rehabilitate switch rather
>> than make a new construct (which would live side by side with the old 
>> construct
>> forever), we have to take into account the preconceived mental models to a
>> greater degree.
>>
>> Totality is a predicate on a pattern and the static type of its match target;
>> for a pattern P to be total on T, it means that all values of T are matched 
>> by
>> P.  Note that when I say “matched by”, I am appealing not necessarily 

Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:47:09 PM
> Subject: Treatment of total patterns (was: Reviewing feedback on patterns in
> switch)

>> 1. Treatment of total patterns in switch / instanceof

> The handling of totality has been a long and painful discussion, trying to
> balance between where we want this feature to land in the long term, and
> people’s existing mental models of what switch and instanceof are supposed to
> do. Because we’ve made the conscious decision to rehabilitate switch rather
> than make a new construct (which would live side by side with the old 
> construct
> forever), we have to take into account the preconceived mental models to a
> greater degree.

> Totality is a predicate on a pattern and the static type of its match target;
> for a pattern P to be total on T, it means that all values of T are matched by
> P. Note that when I say “matched by”, I am appealing not necessarily to “what
> does switch do” or “what does instanceof do”, but to an abstract notion of
> matching.

> The main place where there is a gap between pattern totality and whether a
> pattern matches in a switch has to do with null. We’ve done a nice job
> retrofitting “case null” onto switch (especially with `case null, Foo f` which
> allows the null to be bound to f), but people are still uncomfortable with
> `case Object o` binding null to o.

> (Another place there is a gap is with nested patterns; Box(Bag(String s)) 
> should
> be total on Box>, but can’t match Box(null). We don’t want to 
> force
> users to add default cases, but a switch on Box> would need an
> implicit throwing case to deal with the remainder.)

> I am not inclined to reopen the “should `Object o` be total” discussion; I
> really don’t think there’s anything new to say there. But we can refine the
> interaction between a total pattern and what the switch and instanceof
> constructs might do. Just because `Object o` is total on Object, doesn’t mean
> `case Object o` has to match it. It is the latter I am suggesting we might
> reopen.

> The motivation for treating total patterns as total (and therefore nullable) 
> in
> switch comes in part from the desire to avoid introducing sharp edges into
> refactoring. Specifically, we had two invariants in mind:

> x matches P(Q) === x matches P(var alpha) && alpha matches Q:

> and

> switch (x) {
> case P(Q): A
> case P(T): B
> }

> where T is total on the type of x, should be equivalent to

> switch (x) {
> case P(var alpha):
> switch(alpha) {
> case Q: A
> case T: B
> }
> }
> }

> These invariants are powerful both for linguistic transformation and for
> refactoring.

> The refinements I propose are:

> - Null is only matched by a switch case that includes `case null`. Switches 
> with
> no `case null` are treated as if they have a `case null: throw NPE`. This 
> means
> that `case Object o` doesn’t match null; only `case null, Object o` does.

> - Total patterns are re-allowed in instanceof expressions, and are consistent
> with their legacy form.

> Essentially, this gives switch and instanceof a chance to treat null specially
> with their existing semantics, which takes precedence over the pattern match.

> The consequences of this for our refactoring rules are:

> - When unrolling a nested pattern P(Q), we can only do so when Q is not total.
> - When unrolling a switch over nested patterns to a nested switch, `case P(T)`
> must be unrolled not to `case T`, but `case null, T`.

> These changes entail no changes to the semantics of pattern matching; they are
> changes to the semantics of instanceof/switch with regard to null.

I have a slight preference for the C# syntax, the only way to have a total 
pattern is to use "var" so case P(T) is equivalent to instanceof P p && p.t 
instanceof T t. 
Yes, it's not great because it means that "var" is not just inference but i 
think i prefer that compromise than having a type in a pattern means something 
different if it is nested or not. 

The semantics you are proposing (or the one currently implemented in Java 18) 
is objectively neither worst nor better than the C# one, it's just different. 
Pragmatically, we should choose the C# semantics, just because there are 
already thousands of people who knows it. 

Rémi 


Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:02 PM
> Subject: Positioning of guards (was: Reviewing feedback on patterns in switch)

>> 2.  Positioning of guards
> 
> We received several forms of feedback over the form and placement of guarded
> patterns.  Recall that we define a guarded pattern to be `P && g`, where P is 
> a
> pattern and g is a boolean expression.  Guarded patterns are never total.  
> Note
> that we had a choice of the guard being part of the pattern, or being part of
> the `case` label; the current status chooses the former.  (Part of our
> reasoning was that there might be other partial pattern contexts coming, and 
> we
> didn’t want to solve this problem each time. (For intsanceof, it makes no
> difference.) )
> 
> I am prepared to reconsider the association of the guard with the pattern, and
> instead treat it as part of the case.  This is expressively weaker but may 
> have
> other advantages.
> 
> Additionally, people objected to the use of &&, not necessarily because
> “keywords are better”, but because of the potential confusion should we ever
> choose to support switch over boolean, and because the && did not stand out
> enough as turning a total pattern into a partial one.  What the alternative
> looks like is something like:
> 
>switch (x) {
>case Foo(var x, var y)
>when x == y -> A;
>case Foo(var x, var y) -> B;
>}
> 
> Here, `when` (bike shed to be painted separately) is a qualifier on the case,
> not the pattern.  A total pattern with a `when` is considered a partial case.
> This simplifies patterns, and moves the complexity of guards into switch,
> where arguably it belongs.
> 
> The loss of expressiveness is in not allowing nested patterns like:
> 
>P(Q && guard)
> 
> and instead having to move the guard to after the matching construct.  Some
> users recoiled at seeing guards inside pattern invocations; it seemed to some
> like mixing two things that should stay separate.  (For unrolling a nested
> pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when
> alpha instanceof Q`.)

I think it's a good simplification.

Rémi


Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:08 PM
> Subject: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

>> 3.  Type refinements for GADTs
> 
> There are a number of unsatisfying aspects to how we currently handle GADTs;
> specifically, we are missing the type refinement process outlined in "Simple
> unification-based type inference for GADTs” (SPJ et al, 2005).  Here are some
> examples of where we fall down.
> 
> Suppose we have
> 
>sealed interface Node { }
>record A(T t) extends Node { }
>record B(String s) extends Node { }
> 
> and we want to write:
> 
> T unbox(Node n) {
>return switch (n) {
>case A n -> n.t;
>case B n -> n.s;
>};
>}
> 
> The RHS of all the arrows must be compatible with the return type, which is T.
> Clearly that is true for the first case, but not for the second; the compiler
> doesn’t know that T has to be String if we’ve matched a Node to B.  What is
> missing is to refine the type of T based on matches.  Here, we would gather an
> additional constraint on `case B` for T=String; if we had a case which was
> covariant in T:
> 
>record C(T t)
> 
> then a `case C` would gather an additional constraint of T <: B for its 
> RHS.
> 
> More generally, any code dominated by a match that provides additional bounds
> for type variables could benefit from those bounds.  For example, we’d 
> probably
> want the same in:
> 
>if (n instanceof B b) { /* T is String here */ }
> 
> and (as with flow scoping):
> 
>if (! (n instanceof B b))
>throw …
>// T is String here
> 
> We can probably piggy back the specification of this on flow scoping, 
> appealing
> to “wherever a binding introduced by this pattern would be in scope.”

I agree that we should do something to support GADTs

The instanceof example is not a source backward compatible change, remember 
that instanceof is not a preview feature.

The main objection to that is that we do not have flow scoping for local 
variables but we have it for type variables which is weird.
I wonder if we can come with an explicit syntax for it, the same way instanceof 
String s is an explicit syntax for local variables.

By example, something like
  return switch (n) {
case A n -> n.t;
case B n -> n.s as T=String;
  };

but maybe it's too much ceremony.

regards,
Rémi


Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:12 PM
> Subject: Diamond in type patterns (was: Reviewing feedback on patterns in 
> switch)

>> 4.  Diamond for type patterns (and record patterns)
> 
> 
> The type pattern `T t` declares `t`, if the pattern matches, with the type T.
> If T is a generic type, then we do a consistency check to ensure soundness:
> 
>List list = …
>switch (list) {
>case ArrayList a: A  // ok
>case ArrayList a: B   // ok
>case ArrayList a: C// ok, raw type
>case ArrayList a:  // error, would require unchecked conversion
>   }
> 
> All of these make sense, but users are going to be tempted to use `case
> ArrayList a` rather than the full `case ArrayList a`, and then be sad
> (or confused) when they get type errors.  Since the type can be precisely
> defined by inference, this seems a place for allowing diamond:
> 
>case ArrayList<> a: B
> 
> (And the same when we have record patterns.)

The questions we did not answer the last time we talk about that subject
 - why should we allow raw types here ?
 - given that this is equivalent to an instanceof + cast, why we can not use 
diamond inference on cast ?
 - how this inference work ? Is is the same inference than with the diamond 
constructor ?
   By example, if we have

   List list = ...
   switch(list) {
 case ArrayList<> a:
   }

   Do we really want to infer ArrayList to then rejects it because it's 
an unsafe cast.

regards,
Rémi


GADT and type variable specialization during inference

2021-11-24 Thread Remi Forax
Moved to amber-spec-experts,

I've got a very similar issue, if we support patter matching on Class.

  public Stream createPrimitiveStrem(Class type) {
return switch(type) {
  case int.class -> new SpecializedStream();
  case double.class -> new SpecializedStream();
  ...
};
  }

A cast and a pinky swear @SuppressWarnings("unchecked") solve the issue but we 
may do better.

Rémi

- Original Message -
> From: "Brian Goetz" 
> To: "Jan Lahoda" , "amber-dev" 
> 
> Sent: Lundi 8 Novembre 2021 21:37:24
> Subject: Re: RFR: 8273328: Compiler implementation for Pattern Matching for 
> switch (Second Preview)

> This is more of a spec issue than a compiler issue.  I finally got
> around to running some of my favorite GADT examples on this.
> 
> I started with this Haskell type:
> 
>     data Term t where
>     Lit :: t -> Term t
>     Succ :: Term Int -> Term Int
>     IsZero :: Term Int -> Term Bool
>     If :: Term Bool -> Term a -> Term a -> Term a
> 
> I can map it to this Java hierarchy:
> 
>     sealed interface Term { }
> 
>     record Lit(T val) implements Term { }
>     record Succ(Term a) implements Term { }
>     record IsZero(Term a) implements Term { }
>     record If(Term cond, Term a, Term b) implements
> Term { }
> 
> We correctly eliminate the impossible cases in:
> 
>     String foo(Term t) {
>         return switch (t) {
>     case Lit -> "Lit";
>     case If -> "If";
>     }
>     }
> 
> And the compiler correctly tells me that the switch is exhaustive.  But
> if I try to write an evaluator:
> 
>     static T eval(Term term) {
>     return switch (term) {
>     case Lit t -> t.val;
>     case Succ t -> eval(t.a) + 1;
>     case IsZero t -> eval(t.a) == 0;
>     case If t -> eval(t.cond) ? eval(t.a) : eval(t.b);
>     };
>     }
> 
> 
> I get errors on the Succ and IsZero cases.  In Haskell, the equivalent:
> 
>     eval :: Term t -> t
>     eval (Lit t) = t
>     eval (Succ i) = (eval i) + 1
>     ...
> 
> works correctly because when we match on `eval (Succ i)`, we unify t
> with Int, since that's the only instantiation for t that would work in
> this case.  That's what allows us to return an Int when we're expecting
> a t, because we've already figured out they are the same.
> 
> So in order to make our eval work, we would have to know to _refine_ the
> bounds of T on the RHS of
> 
>     case Succ t -> eval(t.a) + 1;
> 
> where we would say "Succ is Term, and Succ extends Term, so
> T=Integer here".
> 
> In the absence of this, I have to do an explicit boxing plus an
> unchecked cast:
> 
>     static T eval(Term term) {
>     return switch (term) {
>     case Lit t -> t.val;
>     case Succ t -> (T) (Integer) (eval(t.a) + 1);
>     case IsZero t -> (T) (Boolean) (eval(t.a) == 0);
>     case If t -> eval(t.cond) ? eval(t.a) : eval(t.b);
>     };
>     }
> 
> That we need both the boxing and the T cast is probably a bug.
>


Re: Translation musings (was: Are templated string embedded expressions "method parameters" or "lambdas"?)

2021-11-02 Thread Remi Forax
> From: "Brian Goetz" 
> To: "John Rose" 
> Cc: "amber-spec-experts" 
> Sent: Mardi 2 Novembre 2021 18:43:29
> Subject: Translation musings (was: Are templated string embedded expressions
> "method parameters" or "lambdas"?)

> I think we need to redirect a bit.

>> I’m pointing out a different proposition: *If* you need static validation of 
>> a
>> string, *then* you need something constant about the receiver. By constant I
>> mean, very specifically, “fixed no later than just before the first execution
>> of of the string template expression”. The “something constant” might be the
>> type (some subtype of a known “ST-Handler”), or it might be a constant value
>> (e.g., a static final field value).

> And I'm saying something different:

> - We never *need* static validation of the string.
> - We never *need* an indy-based, multi-step translation.

> Both of these are nice to haves, not must-haves; the goal is to extract these
> when we can have them, not to restrict the feature so that we can't use the
> feature if we can't produce them.

> We are in deep danger of way over-rotating towards the translation. I would go
> as far as to say that if we always, now and for the forever future, just
> translated with "invokeinterface", it would *still* be a successful and 
> popular
> feature. Do we want to do better when we can? Of course. Are we willing to
> restrict the feature to the cases where can do so? Absolutely not.

> It's really easy to get wrapped around the axle over cases like 
> String::format,
> and convince ourselves that this is the 99% use case. But this represents only
> a slice (yes, an important slice) of the benefits we get from this feature. In
> many cases, the work we'll do to translate the template (e.g., a CLASS."public
> class Foo { ... }" policy) or after we translate the template (execute a SQL
> query) completely dominates the performance profile.

> So yes, let's optimize for the best-case translation when we can, but let's 
> not
> let that constrain what feature we deliver.
Templated strings can have a awful performance model if we are not careful, and 
i don't think that relying on the user being careful aka the C++ strategy is a 
good strategy. 

They have been enough lengthy discussions about the performance of 
String.format() vs StringBuilder, i think it's reasonable to avoid to create 
new performance pot holes when designing this feature. 

So static validation and being indy-based are a little more than just nice to 
have, they are here to provide an easy to understand performance model. 

regards, 
Rémi 


Re: Are templated string embedded expressions "method parameters" or "lambdas"?

2021-10-31 Thread Remi Forax
> From: "John Rose" 
> To: "Guy Steele" 
> Cc: "Brian Goetz" , "Tagir Valeev" 
> ,
> "Jim Laskey" , "amber-spec-experts"
> 
> Sent: Dimanche 31 Octobre 2021 01:18:38
> Subject: Re: Are templated string embedded expressions "method parameters" or
> "lambdas"?

> On Oct 30, 2021, at 3:22 PM, John Rose < [ mailto:john.r.r...@oracle.com |
> john.r.r...@oracle.com ] > wrote:

>> restrict the operand x (the receiver LHS of the S.T.)
>> to be a statically constant expression. If we do that,
>> then we can hand three things to a bootstrap method
>> that can do static validation:

>> 1. the (constant) receiver
>> 2. the string body (with holes marked)
>> 3. maybe the static types of the arguments (this is very natural for indy)

> Completing the design is pretty straightforward, but I might
> as well write out more of my work. Here’s *one possible* design
> in which the terminal “apply” operation is performed under
> the name “MethodHandle.invokeExact”.

> X."y…\{z…}" translates to an invokedynamic instruction

> The static arguments to the indy instruction are X (formed
> as a CONSTANT_Dynamic constant as necessary) and the
> string body containing y with hole indicators.

> Thus, the indy BSM gets the following:

> 1. a Lookup
> 2. a name (ignored)
> 3. a method-type (composed of the static types of z, returning R the 
> expression
> type)
> 4. X (via condy)
> 5. "y…" where the holes are appropriately marked

> It returns a CallSite, which is then used for all evaluations
> of the expression. Applications will use a ConstantCallSite.

> That is the mechanism. It does not say what is the logic of the BSM
> or the type R. That is where the language rules come in.

> The type of X must contain, directly or not, two or three methods,
> validate, apply, asMethodHandle. The methods are declared as abstracts
> using one or two API types. (Logically, they could also be left “hanging”
> outside of any interface as the magic methods Brian detests.)

One missing point is that it should be possible to do a static analysis of the 
code so asMethodHandle is a way to improve the performance, not to specify or 
change the semantics. 

> I will show one-interface and two-interface potential designs.

> interface ST_A { // 1 type with 3 methods
> ST12 validate(Lookup, String, MethodType);
>  apply(E…);
> MethodHandle asMethodHandle();
> }

It's more an implementation details but if you have the method apply(E...), the 
compiler will generate a bridge method but the implementation will have not way 
to find it, 
you need also to provide the refied argument used as E. We had the same issue 
when generating the lambda proxy. 

> interface ST_B { // 2 types with 1 or 2 methods
> Applier validate(Lookup, String, MethodType);
> interface Applier {
>  apply(Object… E);
> MethodHandle asMethodHandle();
> }
> //default R validateAndApply(Lookup, String, MethodType) { … }
> }

> interface ST_C { // 1 type with 2 methods, plus MH
> MethodHandle validate(Lookup, String, MethodType);
> R validateAndInvoke(Lookup, String, MethodType, Object...);
> }
> // “apply” here is MethodHandle::invokeExact; asMethodHandle is a nop

> The language infers R as usual, as if the call were going through
> apply (A), validate then apply (B) or validateAndInvoke (C).
> But the BSM uses drives the same API points to obtain the
> needed MethodHandle, which is then installed in a CCS.

> Further variations: Have a static hook to produce, not a CCS
> but a general CS such as a MCS. Drop the Lookup argument
> from the APIs, because who wants that? You can add it later.

I think that having the Lookup argument is actually harmful, unlike the way we 
use BSM for lambdas, string concatenation or records where they are defined in 
the JDK, 
here the equivalent of the bootstrap method is implemented in library code. As 
a user, i don't want to pass a Lookup to my class to a library because i'm 
using a templated string, 
this is too much power. 

> The oddity here, as in existing prototypes, is that there are
> “two sets of books”, one for indy with its static bootstrap
> logic that produces a method handle, and one “for the
> police” which shows how all the types (including R) fit
> together.

> All of the above APIs allow implementations (subtypes) of the
> interface to supply different calling sequences for the eventual
> apply (or invoke). This is important, because a logger-oriented
> Applier wants to accept delayed evaluation lambdas if possible,
> while other simpler uses of the S.T. mechanism will be happy
> to get along with just the Object arguments of apply(Object…).

If you want to design a template policy for a logger you wan both to allow 
direct evaluation and delayed evaluation by providing two overloaded methods, 
like 
void log(TemplatedString template, Object... args) 
and 
void log(TemplatedString template, Supplier... args) 

> One of the fine points of this design is whether and how
> to statically type the *hole arguments* and whether the
> static 

Yet another proposal of a templated string design

2021-10-30 Thread Remi Forax
Happy Halloween,
i spend some time again thinking how to implement templated strings.

>From the comments of my previous attempt, the main issues of the previous 
>iteration were that it was a little too magical and by using a bootstrap 
>method force the BSM design into the JLS.
So i try to borrow some aspects of the proposal from Brian and Jim and makes 
them mine.

I still think that Brian and Jim proposal to use an interface and to bind the 
values into the TemplatedString object far from good enough.

A templated string is fundamentally a string with holes (the template part) and 
some arguments (the sub expressions part),
so it should be modeled by a method call.

In a way, a templated string is similar to a varargs call, at the definition 
site, we want a special keyword like the symbol "..." for varargs and at call 
site the compiler do transformation some boxing / array creation in the case of 
varargs.
A templated string is in fact more like the opposite of varargs (a spread 
operator ?) because a templated string is expanded into several parameters, a 
constant TemplatedString and the values of the sub-expressions while the 
varargs collects several arguments into one parameter.

The other thing to remark is that the current syntax, something like 
Format."name: \(name) age: \(age)" omits the method name, so there is need for 
a convention for the compiler to linked a templated string call to an actual 
method definition in a similar way the name "value" is used when declaring an 
annotation without mentioning a method name.

I think we can group those two constraints by using that a method with a 
special name, i will use the hyphenated name "template-policy" in the rest of 
the document, obviously it can be any name. Using an hyphenated name has the 
advantage to be clear at definition site that the method is special and acts as 
a kind of spread operator.

So i propose that
  Format."name: \(name) age: \(age)"

is semantically equivalent to
  Format.template-policy(new TemplatedString("name: \uFFFC age: \uFFFC", ...), 
name, age).result()


You can notice that there is a call to result() on the returned value, it's 
because the returned value can be either a value or a value and a policy 
factory (a lambda to call to optimize the call, in a very similar way 
TemplatePolicy.asMethodHandle works).

So at declaration site the method template-policy looks like that

public class Format {
  public static TemplatePolicyResult template-policy(TemplatedString 
templatedString, Object... args) {
if (templatedString.parameters().size() != args.length) {
  throw new IllegalArgumentException(templatedString + " does not accept " 
+ Arrays.toString(args));
}
var builder = new StringBuilder();
for(var segment: templatedString.segments()) {
  builder.append(switch(segment) {
case Text text -> text.text();
case Parameter parameter -> args[parameter.index()];
  });
}
var text = builder.toString();
return TemplatePolicyResult.result(text);
  }
}

The compiler can check that the expressions of the templated string are 
correctly typed, here they have to be assignable to Object.
The return type, is the type argument of TemplatePolicyResult, so the 
result is a String.

If we want to optimize the template-policy to use the StringConcatFactory, 
instead of just specifying a result as return value,
we can also specify a policy factory.

public class Format {
  public static TemplatePolicyResult template-policy(TemplatedString 
templatedString, Object... args) {
... // see above
var text = builder.toString();
return TemplatePolicyResult.resultAndPolicyFactory(text, 
StringConcat::policyFactory);
  }

  private static MethodHandle policyFactory(TemplatedString templatedString, 
MethodType methodType)
  throws StringConcatException {
var recipe = 
templatedString.template().replace(TemplatedString.OBJECT_REPLACEMENT_CHARACTER,
 '\u0001');
return StringConcatFactory.makeConcatWithConstants(MethodHandles.lookup(), 
"concat", methodType, recipe)
.dynamicInvoker();
  }
}

The semantics is the following, the first time the method template-policy is 
called, if the result also comes with a policy factory,
all subsequent calls will use the method handle returned by the policy factory 
lambda.

Internally at runtime, it means using a MutableCallSite but with the guarantee 
that after one call the target will never change again
(and obviously there is no runtime check needed).

The runtime implementation is available here
  
https://github.com/forax/java-interpolation/tree/master/policy-method/src/main/java/com/github/forax/policymethod

regards,
Rémi


Re: Are templated string embedded expressions "method parameters" or "lambdas"?

2021-10-29 Thread Remi Forax
> From: "Jim Laskey" 
> To: "amber-spec-experts" 
> Sent: Vendredi 29 Octobre 2021 16:10:54
> Subject: Are templated string embedded expressions "method parameters" or
> "lambdas"?

> For our early templated string prototypes , we restricted embedded expressions
> to just basic accessors and basic arithmetic. The intent was to keep things
> easy to read and to prevent side effects . Over time, we began thinking this
> restriction was unduly harsh. More precisely, we worried it that it would
> result in a complex, difficult-to-defend boundary. But we still would like
> users to not rely on side-effects.

> Consequently, a new proposal for embedded expressions - we would allow any 
> Java
> expression with the restriction that you can't use single quotes, double 
> quotes
> or escape sequences. We opted to keep this restriction to allow tools (ex.,
> syntax highlighters) to isolate embedded expressions within strings without
> requiring sophisticated parsing.

> Given that an unprocessed templated string involves at least some deferred
> evaluation, should we frame templated string parameters as being more like
> method parameters (all parameters evaluated eagerly, left to right), or should
> we treat them as lambda expressions, which may capture (effectively final)
> variables from the environment, and evaluate the full parameters expressions
> when they are needed?

> Note too that the effectively final restriction rules out some of the worst
> side-effect offenders, like:

> int x = 0;
> formatter."One \{x++} plus two \{x++} is three \{x}";

> -- even if we intend to then do eager evaluation!

> To help understand the issue, let's look at a simplification of how the two
> different paradigms ( method parameter vs. lambda) might be implemented.
> Example:

> int x = 0;
> int method1() {
> System.out.println("one");
> return 1;
> }

> int method2() {
> System.out.println("two");
> return 2;
> }

> System.out.println("Before TemplatedString");
> TemplatedString ts = "\{x} and \{method1()} and \{method2()}";
> System.out.println("After TemplatedString");
> System.out.println(CONCAT.apply(ts));
> System.out.println("After Policy");

Here, i suppose there is a typo, "CONCAT" should be "formatter". 

The whole point of the syntax is that you can not introduce side effects in 
between the reification of the templated string to an object and the method 
call 
so from the POV of the user, there is only one semantics. 

> To help us evaluating the tradeoffs between the two paradigms, our question to
> the experts is, " What are the ramifications of each ? " Please resist the
> temptation to express a preference for one or the other.

For me, "deferred execution" is not the right way to think about the sub 
expressions of a templated string. 
After all, a guard of a pattern is also a sub expression and we require the 
local variables to be captured, despite the fact that there is no "deferred 
execution". 

We do not want side effects in the guard of a pattern, we can not fully 
guaranteed that in Java but at least we can guarantee that the local variable 
will not change. 
I think the same argument apply to a sub expression of a templated string, we 
do not want side effect in it, so we should ask the local variables used inside 
a sub expression to be effectively final. 

> Thank you.

> Cheers,

> -- Jim

regards, 
Rémi 


Templated String and template policies, why the current design is bad

2021-10-17 Thread Remi Forax
I've recently proposed another way to implement the templated string/template 
policies but i may not have made it clear why i think the current proposal [1] 
is bad.

First, some vocabulary, a templated string is a string with some unnamed 
parameters that are filled with the result of expressions
by example, if we use ${ expr } as escape sequence to introduce an expression
the code

  var a = 3;
  var b = 4;
  "sum ${ a } + ${ b } = ${ a + b }"

can be decomposed into

  - a string template that can be seen either as a string "sum @ + @ = @" with 
a special character (here '@') denoting a hole for each parameter
or an array of strings ["sum ", " + ", " = ", ""] indicating the strings in 
between holes.
  - 3 parameters, param0, param1 and param2 initialized respectively with the 
results of the expressions a, b and a + b

Before talking about the current proposal, let's take a look to the way both 
JavaScript and Scala, implement the string interpolation.

For JavaScript [2], you define a function that the template as an array and as 
many parameters you need
  function foo(templateParts, param0, param1, param2) {
...
  }

JavaScript uses backticks `` to delimit the templated strings and ${} as escape 
sequence
so
  var a = 3;
  var b = 4;
  foo.`sum ${ a } + ${ b } = ${ a + b }`

is equivalent to

  foo(["sum ", " + ", " = ", ""], a, b, a + b)


In Scala, this mostly works the same way, there is a class StringContext that 
correspond to a templated string and you define the function foo as a method of 
StringContext that takes the parameters (in Scala, you can add methods on an 
already existing class using (abusing of) the implicit keyword).

  implicit class FooHelper(val template: StringContext) {  // adds the 
following methods to StringContext
def foo(param0: Any, param1: Any, param2: Any) {
  ...
}
  }

Scala uses quotes "" to delimit the templated string and ${} as escape sequence
so
  val a = 3;
  val b = 4;
  foo."sum ${ a } + ${ b } = ${ a + b }"

is equivalent to
  new StringContext("sum ", " + ", " = ", "").foo(a, b, a + b)



In summary, for both JavaScript and Scala, the generalization of string 
interpolation is a function call which takes the templates string parts as 
first argument and the parameters of the templated string as the other 
parameters.

So in Java, you would assume that
- there is an object that represents a templated string with the holes
- there is a method that takes the templated string as first parameter and the 
parameters of the templated string

But this is not how the proposed design works.

The TemplateString does not represent a string with some holes, it represents 
the string with some holes plus the values of the holes, as if the arguments of 
the parameters were partially applied. The TemplateString acts as a closure on 
the arguments, a glorified Supplier if you prefer.

Because the arguments are already inside the TemplatedString, the 
TemplatePolicy, the function that should take the template and the parameters 
does not declare the types of the parameters.
Which means that there is no way for someone that creates a TemplatePolicy to 
declare the types of the parameters, any parameters is always valid, so there 
is no type safety.

This design is not unknown, this is the GString [4] of Groovy. While it makes 
sense for a dynamic language like Groovy to not have to declare the type of the 
parameters, it makes no sense for a language like Java which is statically 
typed to not have a way to declare the types of the parameters like Scala or 
TypeScript/JavaScript do.

The other issue with the proposed design is that there is no way to declare the 
template policy as a static method, it has to be an instance method 
implementing an interface despite the fact that both JavaScript and Scala* 
support function first and lets the user adds supplementary arguments as a 
secondary mechanism (using currying in Scala and by adding a property on the 
function itself in JavaScript).

There is a good reason to support static methods in Java, a lot of use-cases 
does not requires the template policy to have additional arguments (storing 
them in an instance is not necessary) so forcing the template policy to be 
defined as an instance method means a lot of boilerplate for no good reason.

I hope i've convinced you that the current proposal for string interpolation in 
Java is not the right one.

regards,
Rémi

* for Scala, it's a method on StringContext that acts as a function that takes 
a StringContext as first parameter.

[1] https://bugs.openjdk.java.net/browse/JDK-8273943
[3] 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
[4] https://docs.scala-lang.org/overviews/core/string-interpolation.html
[2] https://docs.groovy-lang.org/docs/latest/html/api/groovy/lang/GString.html



String Interpolation

2021-10-13 Thread Remi Forax
Hi everybody, i've spend some time to think how the String interpolation + 
Policy should be specified and implemented.


The goal is to add a syntax specifying a user defined method to "interpolate" 
(for a lack of better word) a string with arguments.

Given that it's a method, the exact semantics of the interpolation, things like 
how the arguments are escaped, how the formatted string is parsed, is written 
is Java, this will allow to support a wide range of use cases.

This proposal does not differ from the original proposal of Brian and Jim in 
its goal but in the way a user declare the interpolation method(s).

TLDR; you can declare an interpolation method and optionally an interpolation 
bootstrap method if you want a more efficient code at the price of having to 
play with the method handle API.

---

The proposal of Brian and Jim uses an interface to define the policy but in 
this case, using an interface is not what we want.
I think there are two main reasons,
- the interpolation method can be an instance method but can also be a factory 
method, a static method, and an interface can not constraint a static method.
- we want the signature of the interpolation method to be free to use any 
number of parameters of any types, something that can not be specified with 
type parameters in Java.

So let's take a step back and write some examples, as a user of the 
interpolation method, we want to
- be able to specify string interpolation,
  you can notice that this is a static method.

  String name = ...
  int value = ...
  String s = String."name: \(name) age: \(age)";


- we also want to be able to instantiate regex Pattern,
  and have a magic optimisation that creates the Pattern instance only one

  Pattern pattern = Pattern."foo|bar";

- we also want to support instance method, so the interpolation can escape the 
arguments differently depending on the context,
  here by example, escaping differently depending on the database driver.

  String username = ...
  Connection connection = ...
  connection."""
SELECT * FROM users where user == "\(username)"
""";

I think the simplest way to specify an interpolation method is to have a method 
with a special name,
i will use __interpolate__ because i don't want to discuss the exact syntax 
here.

This method can be a static method or an instance method and has a restriction, 
the first parameter has to be a String because the first argument is the 
formatted string.

Here is an example of how the method __interpolate__ inside java.lang.String 
can be written.
To avoid everybody to re-implement the parsing of the formatted string, the 
class java.lang.runtime.InterpolateMetafactory provides a helper method 
"formatIterator" that returns an iterator splitting the formatted string into 
text and binding.


  package java.lang;

  public class String {
...
public static String __interpolate__(String format, Object... args) {
  var i = 0;
  var builder = new StringBuilder();
  var iterator = InterpolateMetafactory.formatIterator(format);
  while(iterator.hasNext()) {
switch(iterator.next()) {
  case Text(var text) -> builder.append(text);
  case Binding binding -> args[i++];
}
  }
  return builder.toString();
}
...
  }

While this is nice, you may think that it's just syntactic sugar and it will 
not be more performant that String.valueOf(), i.e. it will be slow.

That's why the specification allow you to provide a second more optimised 
version of the interpolation method using a method __interpolate__bootstrap__.
This method __interpolate__bootstrap__ is not required, can not replace the 
method __interpolate__, both __interpolate__ and __interpolate__bootstrap__
has to be present and it's a backward compatible change to add a method 
__interpolate__bootstrap__ after the fact, there is no need to recompile
all the client code.

For that the compiler translation rely on invokedynamic to call the method 
bootstrap of the class InterpolateMetafactor that at runtime decide
to trampoline either to the method __interpolate__bootstrap__ or to the method 
__interpolate__ if no __interpolate__bootstrap__ exists.

Here is an example of how a call to the interpolation method of String is 
generated by javac
For the Java code

  String name = ...
  int value = ...
  String s = String."name: \(name) age: \(age)";

the equivalent bytecode is  

  aload_1.  // load name
  iload_2.  // load age
  invokedynamic __interpolate__ (Ljava/lang/StringI)Ljava/lang/String;
java.lang.runtime.InterpolateMetafactory.bootstrap(Lookup, String, 
MethodType, String, MethodHandle):CallSite
[ "name: \(name) age: \(age)", String::__interpolate__(String, 
Object[]):String ]

>From the perspective of the compiler the method __interpolate__ works exactly 
>like a method with a polymorphic method signature (the method annotated with 
>@PolymorphicSignature),
so the descriptor of invokedynamic is created by 

Re: Minor improvement to anonymous classes

2021-10-07 Thread Remi Forax
> From: "Kevin Bourrillion" 
> To: "Maurizio Cimadamore" 
> Cc: "Brian Goetz" , "amber-spec-experts"
> 
> Sent: Jeudi 7 Octobre 2021 22:37:58
> Subject: Re: Minor improvement to anonymous classes

> On Thu, Oct 7, 2021 at 7:37 AM Maurizio Cimadamore < [
> mailto:maurizio.cimadam...@oracle.com | maurizio.cimadam...@oracle.com ] >
> wrote:

>>  X getCloseableFoo();

>> Which kind of works, but it's quite an horrible hack (you introduce a type
>> parameter you don't need - which means compiler will try to infer types, 
>> etc.)

> (Incidentally, we have Error Prone give a warning any time a 
> method/constructor
> type parameter is unused in any of the formal parameter types, and I think the
> results have been good. A method like `emptySet()` has to suppress it, but 
> it's
> a fairly special case.)

Using an "unused" parameter types as return type is not unusual either when 
returning null or when throwing an exception given that both the type of null 
and the "nothing" type can not be expressed in Java. 

See by example the javadoc of Assertions.fail() 
[ 
https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html#fail(java.lang.String)
 | 
https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html#fail(java.lang.String)
 ] 

The other usage i can see is to have a better type inference of the return type 
(avoid an explicit cast) when using a polymorphic signature but i'm not even 
sure javac support it. 

Rémi 

>> On 30/07/2021 15:52, Brian Goetz wrote:

>>> I have been working on a library where I've found myself repeatedly 
>>> refactoring
>>> what should be anonymous classes into named (often local) classes, for the 
>>> sole
>>> reason that I want to combine interfaces with an abstract base class:

>>> interface Foo { ... lots of stuff .. }
>>> abstract class AbstractFoo { ... lots of base implementation ... }

>>> interface RedFoo extends Foo { void red(); }

>>> and I want a factory that yields a RedFoo that is based on AbstractFoo and
>>> implements red(). Trivial with a named class, but there's no reason I should
>>> not be able to do that with an anonymous class, since I have no need of the
>>> name.

>>> We already address this problem elsewhere; there are several places in the
>>> grammar where you can append additional _interfaces_ with &, such as:

>>> class X { ... }

>>> and casts (which can be target types for lambdas.)

>>> These are not full-blown intersection types, but accomodate for the fact 
>>> that
>>> classes have one superclass and potentially multiple interfaces. It appears
>>> simple to extend this to inner class creation expressions:

>>> new AbstractFoo(args) & RedFoo { ... }

>>> This would also smooth out a rough edge refactoring between lambdas and
>>> anonymous classes.

>>> I suspect there are one or two other places in the spec that could use this
>>> treatment.

>>> (Note that this is explicitly *not* a call for "let's do full-blown 
>>> intersection
>>> types"; this is solely about class declaration.)

> --
> Kevin Bourrillion | Java Librarian | Google, Inc. | [ 
> mailto:kev...@google.com |
> kev...@google.com ]


Re: Pattern Matching for switch (Second Preview)

2021-10-01 Thread Remi Forax
> From: "Gavin Bierman" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Vendredi 1 Octobre 2021 14:49:01
> Subject: Re: Pattern Matching for switch (Second Preview)

>> On 30 Sep 2021, at 23:25, Brian Goetz < [ mailto:brian.go...@oracle.com |
>> brian.go...@oracle.com ] > wrote:

>> [ moving to a-s-e ]

>> I get the concern that a type pattern is no longer "just a variable
>> declaration"; that was a nice part of the "patterns aren't really so hard to
>> understand" story. But I think the usability is likely to be not very good.
>> Take this example:

>> sealed interface Node { }
>> record AddNode(Node left, Node right) extends Node { }
>> ...

>> Node ni = ...
>> switch (ni) {
>> case AddNode(Node left, Node right) -> ...

>> There's no instantiation of Node possible here *other than* Node. Which
>> means we are forcing users to either redundantly type out the instantiation
>> (which can get big), or use casts inside the body when they pull things out 
>> of
>> left and right. (And patterns were supposed to make casts go away.) There's
>> almost no case where someone wants a raw type here.

> But surely they should write var here?

yes, here is another example 

List list = ... 
switch(list) { 
case ArrayList al -> ... 

> Gavin

Rémi 


Re: Pattern Matching for switch (Second Preview)

2021-09-30 Thread Remi Forax
> From: "Brian Goetz" 
> To: "Gavin Bierman" 
> Cc: "amber-spec-experts" 
> Sent: Vendredi 1 Octobre 2021 00:25:26
> Subject: Re: Pattern Matching for switch (Second Preview)

> [ moving to a-s-e ]

> I get the concern that a type pattern is no longer "just a variable
> declaration"; that was a nice part of the "patterns aren't really so hard to
> understand" story. But I think the usability is likely to be not very good.
> Take this example:

> sealed interface Node { }
> record AddNode(Node left, Node right) extends Node { }
> ...

> Node ni = ...
> switch (ni) {
> case AddNode(Node left, Node right) -> ...

> There's no instantiation of Node possible here *other than* Node. Which
> means we are forcing users to either redundantly type out the instantiation
> (which can get big), or use casts inside the body when they pull things out of
> left and right. (And patterns were supposed to make casts go away.) There's
> almost no case where someone wants a raw type here.

> We faced this in method references; when referring to Foo::m, we use the 
> target
> type to infer the right parameterization of m. Raw types are a migration
> compatibility thing, but there was no migration compatibility concern with
> method references, nor is there with patterns, since these are new linguistic
> forms.

> What's different here is that we have more type information -- the type of the
> switch target. So we can refine the type pattern with additional information
> from the target. And I can't conceive of a case where the user wouldn't thank
> us for this.
I fully agree with Brian. 

The pattern matching syntax can be visually quite heavy, if we can make the 
pattern part simpler/lighter visually, it's a win. 
If a new syntax does not introduce a new way to declare raw types (which does 
not play well with inference), it's a win. 

Rémi 

> On 9/30/2021 8:25 AM, Gavin Bierman wrote:

>>> 2. Inference for type patterns. This one may be a little controversial, 
>>> because
>>> we already let this ship sail with type patterns in instanceof, but I'm 
>>> pretty
>>> convinced that what we're doing right now is wrong. Currently, if we are
>>> switching on a List, we disallow a type pattern for ArrayList,
>>> because this would require an unchecked conversion. This is right. But if we
>>> have a `case ArrayList a`, the type of `a` is not ArrayList, but raw
>>> ArrayList. This is almost always not what the user wants; there's no 
>>> migration
>>> compatibility here where the switch target was generified but the case 
>>> labels
>>> are not. Like we do with method references, we should infer a reasonable
>>> parameterization of ArrayList from the match target when a "naked" type 
>>> shows
>>> up in a type pattern. (If the user really wants a raw ArrayList, they can
>>> switch on a raw List.)

>>> Fixing this for switch is no problem, as it is in preview, but fixing this 
>>> in
>>> instanceof requires more care, since there may be production code out there.
>>> However, we've generally held that it is OK to infer _more_ specific types 
>>> than
>>> have previously been inferred; I doubt much code would be impacted -- more
>>> likely, some silly casts would go away, since users no longer have to cast 
>>> to
>>> ArrayList.

>> I’m still unsure about this. Type patterns are treated like variable
>> declarations - indeed we went to *a lot* of effort to harmonise all 
>> treatments
>> in the JLS regarding variable declarations. What we got to was very pleasing 
>> -
>> even if I say so myself - pattern variable declarations are just variable
>> declarations with a special treatment for scope. This proposal will break 
>> that
>> because now when a user declares a pattern variable of type ArrayList they 
>> get
>> something else. Would we not prefer some sort of indication from the user 
>> that
>> they want inference here? What if they do want the raw type?


It should be possible to type a switch expression with void

2021-09-26 Thread Remi Forax
There is a bad interaction between a lambda and a switch expression,
a lambda allows its expression to be typed void but a switch expression can not 
be typed void,
so the following code does not compile 

  sealed interface I permits A, B {}
  record A() {}
  record B() {}
  
  public Optional findOneA(List list) {
return list.stream()
  .mapMulti((i, consumer) -> switch(i) {
  case A a -> consumer.accept(a);
  case B b -> {}
  })
  .findFirst();
  }

This bug occurs for with any methods that takes a Consumer has parameter (so 
stream.forEach/peek, Iterator.forEachRemaining etc).

The workaound is to add a pair of curly braces around the switch to transform 
it to a switch statement.

For me, it should be possible to have a switch expression typed void. This is a 
backward compatible change given that the code does not compile otherwise.

regards,
Rémi


Re: Feedback on pattern matching (preview feature)

2021-09-25 Thread Remi Forax
[re-sent to amber-spec-experts instead of amber-dev]

- Original Message -
> From: "Brian Goetz" 
> To: "Vikram Bakshi" , "amber-dev" 
> 
> Sent: Samedi 25 Septembre 2021 17:28:11
> Subject: Re: Feedback on pattern matching (preview feature)

> The example you cite is a peek into a feature not yet implemented (its a
> "and beyond" talk), so not only is there no "as patterns", but no
> patterns yet for which "as patterns" would be sensible.
> 
> When we have deconstruction patterns as per the example, you'll be able
> to provide an optional binding:
> 
>     case Point(var x, var y): // don't care about the point
> 
>     case Point(var x, var y) p: // bind the point too
> 
> which we believe will cover the need in a less "nailed on the side" way.

In that case (pun intended), we may want to make the binding of the type 
pattern optional too.
 
  case Type:  // type pattern with no binding

  case Type variable:  // type pattern + binding


This look like the dual of '_', instead of asking for a binding, make it 
optional.

If we follow that rabbit hole, then we should be able to write

  case Point(var, var):


Rémi

> 
> On 9/25/2021 10:21 AM, Vikram Bakshi wrote:
>> Hello,
>>
>> I was playing around with the new pattern matching and wondered about the
>> current absence of "as-patterns". Are there any plans to bring them to Java
>> in a future JEP?
>>
>> An example use case where they could be useful is demonstrated in the
>> recent video from the official Java YouTube channel (
>> https://youtu.be/UlFFKkq6fyU) at 17:22.
>>
>>
>> Regards,
> > Vikram


Re: Sealed Exception

2021-09-24 Thread Remi Forax
- Original Message -
> From: "Zheka Kozlov" 
> To: "amber-dev" 
> Sent: Vendredi 24 Septembre 2021 10:30:54
> Subject: Sealed Exception

> Hi!

CC amber-spec-experts

> 
> Java 17 compiler forces me to insert an unreachable catch block for the
> base Exception:
> 
> public static void main(String[] args) {
>try {
>f();
>} catch (Ex1 e) {
>e.printStackTrace();
>} catch (Ex2 e) {
>e.printStackTrace();
>} catch (BaseEx e) {
>// Unreachable
>}
> }
> 
> private static void f() throws BaseEx {
> }
> 
> sealed abstract class BaseEx extends Exception permits Ex1, Ex2 {
> }
> 
> Otherwise it doesn't compile. Was this decision intentional? 

I don't think so, it's something we have overlooked.

> If yes, why? If not, can we fix it? I see this as an unfortunate limitation.

I agree, it should be fixed.

> 
> With best regards, Zheka Kozlov.

Regards,
Rémi



Re: String Tapas Redux: Beyond mere string interpolation

2021-09-17 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Jim Laskey" 
> Cc: "amber-spec-experts" 
> Sent: Vendredi 17 Septembre 2021 12:49:30
> Subject: Re: String Tapas Redux: Beyond mere string interpolation

> It's interesting that in both Scala and JavaScript when you define
> custom interpolation policy, you get a collection of Strings instead
> of a single string. So in the article, we see "error: file \{} not
> found" but in Scala/JS you would get List.of("error: file ", " not
> found")). While for localization, having a single string already baked
> might be beneficial, I think in the general case, having separate
> parts may be helpful for interpolation implementations, as no
> additional parsing (search for \{}) would be necessary. Also, the
> placeholder designation with \{} (or whatever else) is quite
> arbitrary.

As John says, the parsing will likely be done once.
And for the implementation, to avoid any handcoded bug riddled code, we can 
provide an iterator or something like that take care of the parsing and making 
it representation agnostic.

[...]

Rémi

> 
> 
> On Fri, Sep 17, 2021 at 5:38 PM Tagir Valeev  wrote:
>>
>> As for custom template processing in JavaScript, see "Tagged
>> templates" section here:
>> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
>>
>> On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev  wrote:
>> >
>> > Btw people say that Scala provides a similar mechanism using
>> > StringContext class:
>> > https://www.scala-lang.org/api/2.12.2/scala/StringContext.html
>> > In fact, Scala's `s` prefix before interpolated literal is a recipe
>> > for interpolation provided by a standard library, not by language. And
>> > it's possible to declare your own recipes. Quite similar to our
>> > proposal.
>> >
>> > Also, there's some frustration in Twitter comments regarding the
>> > article narrative like "We will do it better than existing languages"
>> > (especially because Scala was chosen as an example). Probably, the
>> > wording could be better, with more respect to other languages. Of
>> > course, this doesn't undermine the suggested feature itself, the
>> > feature is great.
>> >
>> > With best regards,
>> > Tagir Valeev.
>> >
>> > On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev  wrote:
>> > >
>> > > Hello!
>> > >
>> > > Just read the proposal. I don't have any useful comments for now. For
>> > > me, the proposal looks great as is. Go ahead and implement it :D
>> > >
>> > > With best regards,
>> > > Tagir Valeev.
>> > >
>> > > On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey  
>> > > wrote:
>> > > >
>> > > > Amber experts,
>> > > >
>> > > > Now that JDK 17 has been plated and left the kitchen, we should have a 
>> > > > look-see
>> > > > at one of the new menu items Brian and I have had on a slow boil for 
>> > > > these last
>> > > > few months; Templated Strings.
>> > > >
>> > > > Before you start shouting out, "Templated Strings? This isn't what I 
>> > > > ordered!
>> > > > The subject said string interpolation!!!", take the time to follow 
>> > > > this link
>> > > > https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md.
>> > > > After reading, we hope you'll see that the offering is much better than
>> > > > interpolation meat and potatoes.
>> > > >
>> > > > Cheers,
>> > > >
> > > > > -- Jim


Re: String Tapas Redux: Beyond mere string interpolation

2021-09-17 Thread Remi Forax
- Original Message -
> From: "John Rose" 
> To: "Jim Laskey" 
> Cc: "amber-spec-experts" 
> Sent: Vendredi 17 Septembre 2021 08:43:29
> Subject: Re: String Tapas Redux: Beyond mere string interpolation

> Yay!
> 
> I agree with Brian’s response to Remi:  Nothing new here
> regarding eval or ASTs.

Ok !

> 
> My favorite wished-for-use-case for templated strings is
> a grammar where the “holes” are grammar actions or
> other configuration points for rules.  This paper made
> me envious for the sake of Java, and also made me think,
> “when we get string templates we can try our own
> shenanigans”:
> 
> http://www.inf.puc-rio.br/~roberto/lpeg/#grammar
> http://www.inf.puc-rio.br/~roberto/docs/peg.pdf
> 
>> We can meet our diverse goals by separating mechanism from
>> policy. How we introduce parameters into string expressions is
>> mechanism; how we combine the parameters and the string into a final
>> result (e.g., concatenation) is policy.
> 
> One might also say we separate wiring from function.  How we introduce
> a segmented string with holes, and also (typed) expressions to fill
> those holes, is the wiring.  (No function yet: We just observe all
> those values waiting for us to do something with them.)  How we
> arrange the static structure of those values verges on function, but
> it’s really just setting up for the moment when we have all the values
> and can run our function.  The function comes in when we have all the
> values (crucially, the dynamic and typed hole-filling values).  At
> that point it’s really “only” a method call, but that method contains
> all the function (the “policy”).  The intermediate step where we
> derive a recipient for the function call, from the static parts
> (strings, and also hole types), is a factory method or constructor
> call, one which creates the receiver object (which is constant for
> that expression, just like a no-capture lambda).

yes, it's a two steps process, first return a method handle (as you say below) 
from the formatted String, then call it with the arguments.

Hum, it's really, really like a bootstrap method, no ?

And we already know that because this is how the StringConcatFactory works, the 
only difference is that the format used by the StringConcatFactory define a 
hole as a special unicode character where here we want to have named holes (for 
the ResourceBundle example).

> 
> It’s important to separate wiring from function in part because
> (a) wiring can be fully understood while (b) function is inherently
> undecidable.  So it’s good (for extensibility, universality) when
> the wiring is really simple and clear, and the transitions into
> the mysterious function-boxes are also really clear.

yes,

> 
> Also, if we focus on wiring that is as universal as possible,
> we can do fancy stuff like grammars with functional actions.
> Otherwise, it’s harder.
> 
> Also, making the function part “only a method call” means
> that whatever resources the language has to make method calls
> be a good notation apply to templated strings.
> 
> Also, since the “wiring part” includes the round-up of the
> static parts of the template expression, it follows that we can
> do lots of interesting “compile time” work in the indy or condy
> instruction that implements the expression as a whole.

amen

> 
> I am gently insisting that the types of the “holes” are part
> of the template setup code because, after all, that’s what the
> indy needs anyway, and it seems a shame to make them all
> be erased to Object and Object[].

or String[] like in Scala.

> 
> One reason “just use Object” is a missed opportunity:  You
> get lots of boxing.  Another, deeper one:  Without FI-based
> target typing that would be provided by a general Java method
> call, you can’t put poly-expressions (like lambdas) into the holes.

The other issue is checked exception, we need a way to allow the possible 
checked exception to be thrown (i have no idea how ?).

> 
> For example, a PEG template might take a varargs argument of
> type (PEGRuleAction… actions) where PEGRuleAction is a FI.
> (Mixing FIs and other data is a challenge I will delay for now,
> but it’s under the rubric of “Varargs 2.0”, allowing methods
> to capture variable-length yet type-heterogeneous arguments.
> Think also for Map.of(…) which wants to take pairs
> of arguments of alternating types.  But that’s for later.)

In my fantasy world, Record** is the type you are looking for, it's how you can 
declare a keyword arguments that that should be generated as a synthetic record 
by the compiler.

void foo(Record** r) ask for a record generated by the compiler so the 
MethodType passed to the bootstrap method will be something like 
($SyntheticRecord$)V

> 
> I’m fully aware that I will be asking for stuff that is beyond
> a 1.0-level feature set, but in discussing stuff like this I’m
> hoping to stake out a path for growth to wider set of use
> cases that inevitably comes if the meaning of the expression
> is 

Re: String Tapas Redux: Beyond mere string interpolation

2021-09-16 Thread Remi Forax
Brian explicitly ask me to not talk about invokedynamic so i will not say that 
there is already an existing protocol between invokedynamic and a user defined 
implementation, it's the bootstrap method, 

Let's talk about the elephant in the room: macro. 
Templated Strings as currently defined is indiscernible from a hygienic String 
based macro system [1]. 

Using javac as an API (like jshell does), it seems trivial to come with 
something like EVAL."\(a) + \(b)" being able to evaluate at runtime any Java 
expressions. 

I'm not sure if it's a good thing or a bad thing. 

Rémi 

[1] https://en.wikipedia.org/wiki/Hygienic_macro 

> From: "Jim Laskey" 
> To: "amber-spec-experts" 
> Sent: Jeudi 16 Septembre 2021 15:28:41
> Subject: String Tapas Redux: Beyond mere string interpolation

> Amber experts,

> Now that JDK 17 has been plated and left the kitchen, we should have a 
> look-see
> at one of the new menu items Brian and I have had on a slow boil for these 
> last
> few months; Templated Strings.

> Before you start shouting out, "Templated Strings? This isn't what I ordered!
> The subject said string interpolation!!!", take the time to follow this link [
> https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md
> |
> https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md
> ] . After reading, we hope you'll see that the offering is much better than
> interpolation meat and potatoes.

> Cheers,

> -- Jim


Re: Factory methods & the language model

2021-09-14 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "daniel smith" , "Dan Heidinga" 
> 
> Cc: "valhalla-spec-experts" 
> Sent: Vendredi 10 Septembre 2021 20:25:50
> Subject: Re: Factory methods & the language model

>> I'm not particularly interested in settling on a bikeshed color, but am
>> interested in the general mood for pursuing this direction at all. (And not
>> necessarily right away, just—is this a direction we think we'll be going?)
>>
>> A few observations/questions:
>>
>> - 'new Foo()' traditionally guarantees fresh instance creation for identity
>> classes. Primitive classes relax this, since of course there is no unique
>> identity to talk about. Would we be happy with a language that relaxes this
>> further, such that 'new Foo()' can return an arbitrary Foo instance (or maybe
>> even null)? Or would we want to pursue a different, factory-specific 
>> invocation
>> syntax? (And if so, should primitive classes use it too?)
> 
> Let me offer some context from Amber that might be helpful, regarding
> whether we might want "factory" to be a language feature.
> 

[...]

> 
> Example 2 -- "with" expressions / reconstructors.  A number of
> interesting features come out of the pairing of constructors and
> deconstruction patterns with the same (or compatible) argument lists,
> such as `with` expressions (`point with { x = 3 }`). Handling this
> involves doing multiple overload selection operations, first to find a
> deconstructor that yields the right bindings, and then to find a
> compatible constructor that accepts the resulting exploded state.
> 
> Among the many problems of doing this (including the fact that parameter
> names are not required to be stable or meaningful), we also have the
> problem of "what if the class doesn't have a constructor, but a
> factory."  This would require the language to have a notion of factory
> method (e.g., `factory newPoint(int x, int y)`) so the compiler could
> try to match up a compatible factory with the deconstructor.

I think there is a way to not introduce a weird with expression syntax by 
piggybacking on the fact that a record is a weird tuple.

A record in Java is not just a tuple, i.e. a vector of values, but because all 
components are named also a compact key/value set.

The "with" expression is a case where we want to see a record as key/value set 
more than as a vector of values.

If we have a syntax to construct a record as a key/value set, this syntax can 
be slightly extended to express a "with" expression.

By example, if we have a syntax like
  Point p = Point { x: 3, y: 4 };

Then the syntax for the with expression will be to something like
  Point p2 = Point { x: 7, p };

I can hear you saying that i'm trying to trick you to add a new syntax for 
creating a record which is bigger that just the "with" expression.
And that's partially true.

I believe that the "key/value set" syntax for a record is something we should 
introduce in Amber anyway because it's a declarative syntax, the same way a 
Stream is, making the code easier to read. And people want a syntax like this 
so badly that they are up to writing a full builder pattern in their code just 
for being able to see the name of the parameters when creating complex objects.

regards,
Rémi


merging cases in between values and types

2021-09-08 Thread Remi Forax
Hi all,
the current spec support merging/fallthrough cases only when there is a case 
null,
like
  case null, case Integer i -> // i can use 'i' here
  
First, i'm not sure it's clearly written in the spec, i was not able to 
pinpoint the rule allowing it.
Then i wonder if this special behavior is special because null is special or if 
it should work with any values of the type of the type pattern,
i.e. it works because null is a possible value of Integer, in that case, it 
should also work with any other values like 42

  case 42, case Integer i ->  // i can use 'i' here

So is null a special value or does this rule also work with any values.

regards,
Rémi




Re: Reiterate total pattern accepting null in switch

2021-09-06 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "Tagir Valeev" , "amber-spec-experts" 
> 
> Sent: Lundi 6 Septembre 2021 15:46:19
> Subject: Re: Reiterate total pattern accepting null in switch

> On 9/6/2021 5:12 AM, Tagir Valeev wrote:
>> Hello!
>>
>> Now, as we develop support in IntelliJ, we have a little bit of
>> experience with patterns in switches. So far, the thing I dislike the
>> most is that the total pattern matches null in the switch. I shared my
>> concerns before and now they are basically the same, probably even
>> stronger. Note that I'm not against total pattern matching null in
>> general (e.g., as a nested pattern in deconstruction). But I still
>> think that matching it at the top level of the switch is a mistake.
>> Mentally, the total pattern is close to the default case.
> 
> I think there are several issues here; let's try to tease them apart.
> 
> The main problem, as I see it, is not one of whether we picked the right
> default, or whether that default is unfamiliar (though these are both
> valid things to discuss), but that we are putting the user to a
> non-orthogonal choice.  They can say:
> 
>     case Object o:
> 
> and get binding and null-matching, or
> 
>     default:
> 
> and get neither binding nor null-matching.
> 
> In some way, you are saying that there is a significant contingency
> where users want binding but not null-matching, and we're forcing users
> to take a package deal.  As a thought experiment, how differently would
> you feel if we had both nullable and non-nullable type patterns:
> 
>     case String! s:
>     case String? s:
> 
> If we had the ability to refine the match in this way, then the choice
> of binding and null handling would be orthogonal:
> 
>     case Object! o:   // binding, no null
>     case Object? o:   // binding, null
>     default:  // no binding, no null
>     null, default:    // no binding, null
> 
> So the first thought experiment I am asking you to do is whether, in
> this world, you would feel significantly differently.


There is no binding available for "default" but the selector value of the 
switch is available.

switch(anObject) {
  default:   // no binding but one can use 'anObject' directly
}

so i'm not sure to fully understand the point your are trying to make.

> 
>> Also,
>> adding a guard means that we do not receive null at this branch
>> anymore which is also confusing.
> 
> Good point, I'll think on this a bit.

yes,
if there is no default, the type pattern for a case which is not the last and 
the type pattern for the last case has a different meaning (for the record i'm 
fine with that),
but combined with the current rules for the guarded patterns, this has some 
puzzling side effects.

By example, using a guard that is simple enough for a human to see it is always 
true but not simple enough for the compiler so it is not constant folded,
  switch(x) {
... other cases
case Object obj && x == x -> ... // exclude null
case Object obj -> // null goes here
  }

> 
> To reiterate the motivation, the thing we're going for here is the
> consistency that a switch of nested patterns and a nested switch are the
> same:
> 
>     case Box(Foo f):
>     case Box(Object o):
> 
> is the same as
> 
>     case Box b:
>     switch (b.get()) {
>     case Foo f:
>     case Object o:
>     }
>     }
> 
> If we treat the null at the top level, we get a different kind of
> asymmetry.  What we're banking on (which could be wrong) is that its
> better to rip off the band-aid rather than cater to legacy assumptions
> about switch.

And also we want to keep the legacy semantics of "default", we do not want the 
existing "default" inside the switch on String/Enum to have a different 
behavior from the existing ones.

> 
> I think what you are saying here is that switch is so weird that it is
> just a matter of pick your asymmetry, and the argument for moving it to
> the top level is that this is weirdness people are already used to.
> This may be true, though I worry that it is just that people are not
> *yet* used to nested switches, but they'll be annoyed when they get bit
> by refactoring issues.

And there is also actually no way to write "case var o" which is IMHO the 
explicit way to convey the fact that the last case is total.
Using "case var o" in all the examples above make the codes readable.

I think the real issue here is that we are in the middle of introducing the 
different patterns, so the big picture is not in front of us yet.

[...]

Rémi


switch expression with not explicit yield value should work ?

2021-08-29 Thread Remi Forax
Another case where the spec is weird,
i've converted a project that generate a visitor from a grammar (something like 
yacc) to use a switch on type instead.

Sometimes for a degenerate portion of the grammar i've an empty visitor that 
always throw an exception,
the equivalent code with a switch is

  static Object result(Object o) {
return switch (o) {
  case Object __ -> throw new AssertionError();
};
  }


Obviously i can tweak the code generator to generate

  static Object result(Object o) {
throw new AssertionError();
  }

but not be able to compile the former code strike me as odd.

An expression switch is a poly-expression, so the result type is 
back-propagated from the return type of the method result, so it should be 
Object.

Moreover, if the switch is not a switch expression but a switch statement, the 
code is also valid

  static Object result(Object o) {
switch (o) {
  case Object __ -> throw new AssertionError();
}
  }

Not be able to compile a switch expression when there is no explicit result 
type but only an implicit type seems arbitrary to me
(this change is backward compatible because it only makes more codes compiling).

Rémi


Re: Switch coverage with multiple branches

2021-08-26 Thread Remi Forax
Here is another example with i believe the same issue

  sealed interface Vehicle {}
  record Car(String owner, String color) implements Vehicle {}
  record Bus(String owner) implements Vehicle {}

  public static void example2() {
var vehicles = List.of(
new Car("Bob", "red"),
new Bus("Ana")
);
for(var vehicle: vehicles) {
  switch(vehicle) {
case Car car -> System.out.println("car !");
case Bus bus -> System.out.println("bus !");
case Record record -> throw new AssertionError();
  }
}
  }

This code currently compiles even if the last case is not reachable.

Rémi

- Original Message -
> From: "daniel smith" 
> To: "amber-spec-experts" 
> Cc: "Gavin Bierman" 
> Sent: Samedi 24 Juillet 2021 00:28:08
> Subject: Switch coverage with multiple branches

> An RFE for JEP 406 (or maybe bug fix? I haven't dug into what the spec says).
> Can we make this compile?
> 
> public class SwitchCoverage {
>sealed interface A {}
>sealed interface B1 extends A {}
>sealed interface B2 extends A {}
>sealed interface C extends A {}
>final class D1 implements B1, C {}
>final class D2 implements B2, C {}
>
>void test(A arg) {
>int i = switch (arg) {
>case B1 b1 -> 1;
>case B2 b2 -> 2;
>};
>}
>
> }
> 
> Output:
> 
> % -> `javahome 17`/bin/javac --enable-preview --release 17 SwitchCoverage.java
> SwitchCoverage.java:10: error: the switch expression does not cover all 
> possible
> input values
>int i = switch (arg) {
>^
> Note: SwitchCoverage.java uses preview features of Java SE 17.
> Note: Recompile with -Xlint:preview for details.
> 1 error
> 
> The compiler wants to see a 'case C c', not realizing that the combination of 
> B1
> and B2 already covers C.
> 
> The use case might seem a impractically complex, but I think it's actually
> pretty normal to want to define a universe of values (that's A), provide some
> implementations (D1 and D2), and then categorize the implementations in
> different dimensions (B1/B2 in one dimension, C in another). When I'm writing
> my switch, I might only care about one of these dimensions.


Re: Minor improvement to anonymous classes

2021-07-31 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Vendredi 30 Juillet 2021 16:52:23
> Subject: Minor improvement to anonymous classes

> I have been working on a library where I've found myself repeatedly 
> refactoring
> what should be anonymous classes into named (often local) classes, for the 
> sole
> reason that I want to combine interfaces with an abstract base class:

> interface Foo { ... lots of stuff .. }
> abstract class AbstractFoo { ... lots of base implementation ... }

> interface RedFoo extends Foo { void red(); }

> and I want a factory that yields a RedFoo that is based on AbstractFoo and
> implements red(). Trivial with a named class, but there's no reason I should
> not be able to do that with an anonymous class, since I have no need of the
> name.

> We already address this problem elsewhere; there are several places in the
> grammar where you can append additional _interfaces_ with &, such as:

> class X { ... }

> and casts (which can be target types for lambdas.)

> These are not full-blown intersection types, but accommodate for the fact that
> classes have one superclass and potentially multiple interfaces. It appears
> simple to extend this to inner class creation expressions:

> new AbstractFoo(args) & RedFoo { ... }

> This would also smooth out a rough edge refactoring between lambdas and
> anonymous classes.

> I suspect there are one or two other places in the spec that could use this
> treatment.

> (Note that this is explicitly *not* a call for "let's do full-blown 
> intersection
> types"; this is solely about class declaration.)
About the proposed syntax, i'm not sure using '&' is a good idea, it may clash 
with operators overloading if we allow them on primitive classes in the future. 
(i believe the compiler may be able to differentiate between the two because of 
the curly braces later but for a human it will be hard to make the difference 
without precisely reading the code). 

Philosophically, I think we should not add more features to inheritance, 
inheritance is the goto of OOP, yes there are some corner cases where it's kind 
a useful but at the same time you are pushing more people to discovering that 
inheritance trees are a mess to maintain because the behavior is spitted to 
several places and hard to refactor too. 

new RedFoo() { + delegation } leads to a code easier to understand and easier 
to maintain. 

Rémi 


Re: Minor improvement to anonymous classes

2021-07-31 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Samedi 31 Juillet 2021 05:55:47
> Subject: Re: Minor improvement to anonymous classes

> By the way, assuming that we implement this, new IFace() {...} would
> be technically equivalent to new Object(), IFace {...}. So this
> essentially means that specifying the Object superclass is optional
> "when interface is present". We can go a little bit further:
> 
> 1. Allow new IFace {...}, without empty parentheses, as there's
> technically no constructor in the interface we are delegating to.
> Instead, we are delegating to the Object constructor, but Object is
> not mentioned explicitly, so these () always looked confusing to me.
> We will still allow new IFace() {...} for compatibility.

It's not only confusing for you, it's confusing for my students too,
how the compiler is able to call the constructor of an interface is a question 
i get each year.

> 
> With best regards,
> Tagir Valeev.

Rémi

> 
> On Sat, Jul 31, 2021 at 10:44 AM Tagir Valeev  wrote:
>>
>> I support this suggestion. I stumbled upon this problem many times.
>> Kotlin allows such declarations [1]
>> object : AbstractFoo(args), RedFoo {...}
>> Seems there's no conceptual difficulties with this enhancement.
>>
>> With best regards,
>> Tagir Valeev.
>>
>> [1] https://kotlinlang.org/spec/expressions.html#object-literals
>>
>> On Fri, Jul 30, 2021 at 9:53 PM Brian Goetz  wrote:
>> >
>> > I have been working on a library where I've found myself repeatedly 
>> > refactoring
>> > what should be anonymous classes into named (often local) classes, for the 
>> > sole
>> > reason that I want to combine interfaces with an abstract base class:
>> >
>> > interface Foo { ... lots of stuff .. }
>> > abstract class AbstractFoo { ... lots of base implementation ... }
>> >
>> > interface RedFoo extends Foo { void red(); }
>> >
>> > and I want a factory that yields a RedFoo that is based on AbstractFoo and
>> > implements red().  Trivial with a named class, but there's no reason I 
>> > should
>> > not be able to do that with an anonymous class, since I have no need of the
>> > name.
>> >
>> > We already address this problem elsewhere; there are several places in the
>> > grammar where you can append additional _interfaces_ with &, such as:
>> >
>> > class X { ... }
>> >
>> > and casts (which can be target types for lambdas.)
>> >
>> > These are not full-blown intersection types, but accomodate for the fact 
>> > that
>> > classes have one superclass and potentially multiple interfaces.  It 
>> > appears
>> > simple to extend this to inner class creation expressions:
>> >
>> > new AbstractFoo(args) & RedFoo { ... }
>> >
>> > This would also smooth out a rough edge refactoring between lambdas and
>> > anonymous classes.
>> >
>> > I suspect there are one or two other places in the spec that could use this
>> > treatment.
>> >
>> > (Note that this is explicitly *not* a call for "let's do full-blown 
>> > intersection
>> > types"; this is solely about class declaration.)
>> >


Re: Minor improvement to anonymous classes

2021-07-31 Thread Remi Forax
> From: "John Rose" 
> To: "Tagir Valeev" 
> Cc: "Brian Goetz" , "amber-spec-experts"
> 
> Sent: Samedi 31 Juillet 2021 20:00:20
> Subject: Re: Minor improvement to anonymous classes

> On Jul 30, 2021, at 8:55 PM, Tagir Valeev < [ mailto:amae...@gmail.com |
> amae...@gmail.com ] > wrote:

>> 2. Allow new {...} instead of new Object() {…}.

> I would think *that* would make a very useful poly expression.
> Building “new Object() {…}” has far fewer use cases than
> “build me an object of the type the context requires, with
> these methods”.

> My overall reaction to the extra syntax tweaks to the
> existing AIC syntax is, “meh”. The syntax is inherently
> ceremonious; why trouble to elide an extra token at
> the head?

and i would prefer to keep that syntax available if at some point in the future 
we decide to allow classes (or perhaps just record) to be initialized in a 
literal way. 

record Person(String name, int age) {} 

var person = new Person { name = "Ana", age = 31 }; 
Person person = new { name = "Ana", age = 31 }; 

which is more readable once you starts to have more than 3 or 4 components (and 
avoid builders). 

Rémi 


Re: Minor improvement to anonymous classes

2021-07-31 Thread Remi Forax
- Original Message -
> From: "John Rose" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Samedi 31 Juillet 2021 00:14:46
> Subject: Re: Minor improvement to anonymous classes

> It seems reasonable:  If you can do this for lambdas,
> which are (sort of) a concise anonymous inner class
> notation, you should be able to do this with the next
> level of notation, anonymous inner classes.  It’s
> ungraceful to have MI on lambdas and named inner
> classes, but not the intermediate notation.
> 
> Any similar limitations to consider loosening at this time?
> Are there things with generics you can do with lambdas
> and named ICs but not AICs?

There is a similar limitation, anonymous classes and local classes always 
capture the enclosing "this" even if they do not use it,
so when you use an anonymous class, the lifetime of the enclosing this is 
extended for no good reason.
The usual issue it to have a code that relies on the finalizer to close a 
system resource (which is not a good idea) that fails to close the resource 
because the is captured by the anonymous class. Lambdas do not have this weird 
behavior.

We have discussed about allowing static in front of a local class but there is 
no way to ask for a static anonymous class.

regards,
Rémi


> 
>> On Jul 30, 2021, at 7:52 AM, Brian Goetz  wrote:
>> 
>> I have been working on a library where I've found myself repeatedly 
>> refactoring
>> what should be anonymous classes into named (often local) classes, for the 
>> sole
>> reason that I want to combine interfaces with an abstract base class:
>> 
>> interface Foo { ... lots of stuff .. }
>> abstract class AbstractFoo { ... lots of base implementation ... }
>> 
>> interface RedFoo extends Foo { void red(); }
>> 
>> and I want a factory that yields a RedFoo that is based on AbstractFoo and
>> implements red().  Trivial with a named class, but there's no reason I should
>> not be able to do that with an anonymous class, since I have no need of the
>> name.
>> 
>> We already address this problem elsewhere; there are several places in the
>> grammar where you can append additional _interfaces_ with &, such as:
>> 
>> class X { ... }
>> 
>> and casts (which can be target types for lambdas.)
>> 
>> These are not full-blown intersection types, but accomodate for the fact that
>> classes have one superclass and potentially multiple interfaces.  It appears
>> simple to extend this to inner class creation expressions:
>> 
>> new AbstractFoo(args) & RedFoo { ... }
>> 
>> This would also smooth out a rough edge refactoring between lambdas and
>> anonymous classes.
>> 
>> I suspect there are one or two other places in the spec that could use this
>> treatment.
>> 
>> (Note that this is explicitly *not* a call for "let's do full-blown 
>> intersection
>> types"; this is solely about class declaration.)
>> 


Re: Switch coverage with multiple branches

2021-07-23 Thread Remi Forax
- Original Message -
> From: "daniel smith" 
> To: "amber-spec-experts" 
> Cc: "Gavin Bierman" 
> Sent: Samedi 24 Juillet 2021 00:28:08
> Subject: Switch coverage with multiple branches

> An RFE for JEP 406 (or maybe bug fix? I haven't dug into what the spec says).
> Can we make this compile?
> 
> public class SwitchCoverage {
>sealed interface A {}
>sealed interface B1 extends A {}
>sealed interface B2 extends A {}
>sealed interface C extends A {}
>final class D1 implements B1, C {}
>final class D2 implements B2, C {}
>
>void test(A arg) {
>int i = switch (arg) {
>case B1 b1 -> 1;
>case B2 b2 -> 2;
>};
>}
>
> }
> 
> Output:
> 
> % -> `javahome 17`/bin/javac --enable-preview --release 17 SwitchCoverage.java
> SwitchCoverage.java:10: error: the switch expression does not cover all 
> possible
> input values
>int i = switch (arg) {
>^
> Note: SwitchCoverage.java uses preview features of Java SE 17.
> Note: Recompile with -Xlint:preview for details.
> 1 error
> 
> The compiler wants to see a 'case C c', not realizing that the combination of 
> B1
> and B2 already covers C.
> 
> The use case might seem a impractically complex, but I think it's actually
> pretty normal to want to define a universe of values (that's A), provide some
> implementations (D1 and D2), and then categorize the implementations in
> different dimensions (B1/B2 in one dimension, C in another). When I'm writing
> my switch, I might only care about one of these dimensions.

Should it compile if C is declared as non-sealed ?

Rémi


Re: case null and type pattern

2021-07-19 Thread Remi Forax
> From: "Brian Goetz" 
> To: "Gavin Bierman" , "Manoj Palat"
> 
> Cc: "amber-spec-experts" 
> Sent: Lundi 19 Juillet 2021 15:00:16
> Subject: Re: case null and type pattern

> Right. In theory, we could allow guarded patterns here, but they will be hard 
> to
> use, since users will likely want to use the binding in the guard, and would
> have to check for nullity every time.
As you said, the error is to be able to use the binding not to be able to 
express that pattern. 

Rémi 

> On 7/19/2021 6:16 AM, Gavin Bierman wrote:

>> Hi Manoj,

>> This is certainly something we can discuss for Preview 2, but…it is 
>> intentional
>> for now. The `case null, T t` is really a special case of extending a type
>> pattern to be null friendly. Remember that the pattern variable `t` is
>> initialised with null if this case label applies.

>> The problem with what you are suggesting is that we’re going to end up with 
>> code
>> like:

>> switch(o) {
>> …
>> case null, String s && s != null -> …
>> }

>> Now the developer has a guard saying that `s` is not null, and is probably 
>> going
>> to assume this in the body of the switch rule, but has erroneously extended 
>> the
>> case label with null.

>> So, it’s just an attempt to remove potential puzzlers by design. But we can 
>> take
>> a look at whether we can capture a more generous definition. An alternative
>> would be to define a new null-friendly type pattern. Let’s talk about it!

>> Thanks,
>> Gavin

>>> On 14 Jul 2021, at 23:43, Manoj Palat [ mailto:manoj.pa...@in.ibm.com |
>>>  ] wrote:

>>> Hi Gavin, All,

>>> In [
>>> http://cr.openjdk.java.net/~gbierman/jep406/jep406-20210608/specs/patterns-switch-jls.html#jls-14.30.1
>>> |
>>> http://cr.openjdk.java.net/~gbierman/jep406/jep406-20210608/specs/patterns-switch-jls.html#jls-14.30.1
>>> ] , Section 14.11, I see "If a switch label has a null case label element 
>>> then
>>> if the switch label also has any pattern case element labels, they must be 
>>> type
>>> patterns (14.30.1)." implying that the following code:

>>> private static void foo(Object o) {
>>> switch (o) {
>>> case null, Integer i && i > 10 -> System.out.println(0); // flag error?
>>> default -> System.out.println(o);
>>> }
>>> should have an error flagged for case label with null since the pattern case
>>> label element is not a type pattern but a guarded pattern.
>>> Any reason for excluding guarded patterns here? Or does this requires a
>>> modification in spec to include guarded patterns as well?

>>> Regards,
>>> Manoj


Re: case null and type pattern

2021-07-18 Thread Remi Forax
- Original Message -
> From: "Manoj Palat" 
> To: "amber-spec-experts" 
> Sent: Jeudi 15 Juillet 2021 00:43:40
> Subject: case null and type pattern

> Hi Gavin, All,

HI Manoj,

> 
> In
> http://cr.openjdk.java.net/~gbierman/jep406/jep406-20210608/specs/patterns-switch-jls.html#jls-14.30.1,
> Section 14.11, I see "If a switch label has a null case label element then if
> the switch label also has any pattern case element labels, they must be type
> patterns (14.30.1)." implying that the following code:
> 
> private static void foo(Object o) {
> switch (o) {
> case null, Integer i && i > 10 -> System.out.println(0); // flag error?
> default -> System.out.println(o);
> }
> should have an error flagged for case label with null since the pattern case
> label element is not a type pattern but a guarded pattern.
> Any reason for excluding guarded patterns here? Or does this requires a
> modification in spec to include guarded patterns as well?

For me, a guarded pattern or a parenthesis pattern are kind of composite 
patterns,
you need to take a look at what's inside. 

But I'm letting Gavin answer to your specific questions.

> 
> Regards,
> Manoj

regards,
Rémi


TypePattern and "var variable"

2021-06-29 Thread Remi Forax
I was re-reading JEP 405,
'var foo' is accepted by the grammar as Type Pattern but
there is no section explicitly mentioning that.

Maybe i'm wrong and 'var foo' is already supported in Java 17,
if it's not the case, i think we should add a section about the Type Pattern 
being enhanced to support 'var'.

regards,
Rémi


Re: Experience with sealed classes & the "same package" rule

2021-06-22 Thread Remi Forax
> De: "John Rose" 
> À: "Maurizio Cimadamore" 
> Cc: "daniel smith" , "amber-spec-experts"
> 
> Envoyé: Mardi 22 Juin 2021 02:31:13
> Objet: Re: Experience with sealed classes & the "same package" rule

> That argument does not make sealing
> less useful or more dangerous in a
> non-modular setting, in a manner
> unique to sealing. So, I still fail to see
> why the proposed simplification has
> any downside at all.

The proposed simplification allows different packages to share different part 
of the sealed hierarchy without a module. 
So those packages can be in different jars, compiled at different times. 
This will produce "impossible" sealed hierarchies where by example two types 
are both permitted subtypes of each other. 

We can save a lot of test and debugging time to a lot of people by avoiding 
split sealed hierarchy. 

> — John

Rémi 


Re: Experience with sealed classes & the "same package" rule

2021-06-09 Thread Remi Forax
> De: "John Rose" 
> À: "daniel smith" 
> Cc: "amber-spec-experts" 
> Envoyé: Jeudi 10 Juin 2021 00:29:40
> Objet: Re: Experience with sealed classes & the "same package" rule
> (Also on the same hit-list: The restriction against
> using local sealed hierarchies. The restriction
> doesn’t make any sense, logically. And it is a
> sharp edge when you copy-and-paste a hierarchy
> as a unit.)

yes, local sealed hierarchies should be allowed if they are inside the same 
scope, it's infuriating to have to move the hierarchy as members of the class 
when you want to write a unit test with everything confined in the same method. 
The same also goes for annotations which are not allowed to be local likewise 
you can not declare them inside a method of a unit test. 

> — John

Rémi 


Re: Experience with sealed classes & the "same package" rule

2021-06-09 Thread Remi Forax
- Mail original -
> De: "daniel smith" 
> À: "amber-spec-experts" 
> Envoyé: Mercredi 9 Juin 2021 23:42:16
> Objet: Experience with sealed classes & the "same package" rule

> Here's some late feedback on the sealed classes preview. Probably actionable
> before 17 GA, if that's what people want to do, or potentially a change that
> can come in a future release. (Or you can just tell me that this inconvenience
> isn't a big deal, live with it.)
> 
> The spec for sealed classes contains this rule:
> 
> ---
> 
> If a sealed class C belongs to a named module, then every class named in the
> permits clause of the declaration of C must belong to the same module as C;
> otherwise a compile-time error occurs.
> 
> If a sealed class C belongs to an unnamed module, then every class named in 
> the
> permits clause of the declaration of C must belong to the same package as C;
> otherwise a compile-time error occurs.
> 
>> Sealed class hierarchies are not intended to be declared across different
>> maintenance domains. Modules cannot depend on each other in a circular 
>> fashion,
>> yet a sealed class and its direct subclasses need to refer to each other in a
>> circular fashion (in permits and extends clauses, respectively). Necessarily,
>> therefore, a sealed class and its direct subclasses must co-exist in the same
>> module. In an unnamed module, a sealed class and its direct subclasses must
>> belong to the same package.
> 
> ---
> 
> A unique property of this rule is that it is guarded on whether code lives in 
> a
> (named) module or not. If you're in a module, you get language semantics X; if
> not, you get language semantics Y. In particular, there exist programs that
> will compile when packaged in a module, but will not when left to the unnamed
> module. (This is different than the canonical use of modules, which is to
> restrict the set of packages that are observable, depending on the
> configuration of the current module.)
> 
> In practice, I had this experience:
> 
> 1) Trying to deploy some Java code with a tool that requires a single jar.
> 2) Need to include the experimental bytecode API, which is packaged as a 
> module.
> 3) Copy the needed API sources into my own source tree.
> 4) Compilation fails: lots of "cannot extend a sealed class in a different
> package" errors.
> 
> Unfortunately, the bytecode API was making use of sealed classes, and those
> hierarchies crossed package boundaries. (In a typical public API vs. private
> implementation packaging strategy.)
> 
> (The workaround was to declare my own module-info.java and package my program 
> as
> a single modular jar.)
> 
> Is this behavior consistent with the design of modules? I lean towards "no":
> Java sources don't opt in to modules. They belong to a module, or not,
> depending on the context—whether there's a module-info.java somewhere, which
> javac parameters are used. The sources themselves are meant to be portable. 
> But
> this rule means any sealed class hierarchy that crosses package boundaries is
> asserting *in the program source* that the classes must belong to a module.


I don't follow you here, a module-info.java is part of the source, it ends with 
.java after all, so like any .java files if you don't copy them, the behavior 
of the library will change.

As an example independent of sealed types, if there is no module-info, packages 
are open, if there is a module-info, packages are not open.
Thus the behavior of a Java library change depending if there is a module-info 
or not.

So the fact that the behavior of sealed type change if there is a module-info 
or not, is consistent with the design of modules.


Sealed hierarchies are restricted to a package in the unamed module in order to 
ease the migration when you add a module-info because a sealed hierarchy 
restricted to a package is obviously restricted to a module.
If you relax that rule, you add another hindrance to the adoption of modules.

regards,
Rémi


case null vs case dominance

2021-06-07 Thread Remi Forax
Hi all,
the first part of the message is about javac error message that could be 
improved,
the second part is about the current spec being not very logical.

With this code

Object o = null;
var value = switch(o) {
  //case null -> 0;
  case Object __ -> 0;
  case null -> 0;
};
System.out.println(value);

The error message is
  PatternMatching101.java:70: error: this case label is dominated by a 
preceding case label
  case null -> 0;
  ^

The error message is wrong here, because it's 'case null' and you can put a 
case null where you want but below a total pattern, so the error mesage should 
reflect that.

Here is an example that compiles showing that case null can be below a case 
String, which it dominates

Object o = null;
var value = switch(o) {
  case String s -> 0;
  case null -> 0;
  default -> 0;
};

Also with default, the spec says that the code below should compile (and it 
works with javac),
because default and case null are disjoint in term of type, but it feels wrong 
to me.

Object o = null;
var value = switch(o) {
  default -> 0;
  case null -> 0;
};
System.out.println(value);

The problem is that sometimes 'default' acts as a total pattern sometimes it 
does not, if there is a case null. I wonder if it's not better to ask users to 
put the case null on top.

Rémi




Exhaustiveness mixing sealed type and enum

2021-06-02 Thread Remi Forax
Do we agree that the code below defines an exhaustive switch so no default is 
necessary ?

sealed interface List permits Cons, Nil { }
record Cons(String value, Object next) implements List { }
enum Nil implements List { NIL }

int size(List list) {
  return switch(list) {
case Cons cons -> 1 + size(cons.next);
case Nil.NIL -> 0
  };
}

regards,
Rémi


Re: Rehabilitating switch -- a scorecard

2021-05-19 Thread Remi Forax
> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Mercredi 19 Mai 2021 13:12:43
> Objet: Re: Rehabilitating switch -- a scorecard

> So, here's another aspect of switches rehabilitation, this time in terms of
> syntactic rewrites. By way of analogy with lambdas, there's a sequence of

> x -> e // parens elided in unary lambda

> is-shorthand-for

> (x) -> e // types elided

> is-shorthand-for

> (var x) -> e // explicit request for inference

> is-shorthand-for

> ( x) -> e // explicit types

> That is, there is a canonical (lowest) form, and the various shorthands form a
> chain of embeddings. The chain shape reduces cognitive load on the user,
> because instead of thinking "there are seven forms of lambda", they can 
> instead
> think there is single canonical form, with progressive options for leaving
> things out / mushing things together.

> We get more of a funnel with the syntax of switch:

> case L, J, K -> X;

> is-shorthand-for

> case L, J, K: yield X; // expression switch, X is an expression
> case L, J, K: X; // expression switch, X is a block
> case L, J, K: X; break; // statement switch

> and

> case L, J, K: X;

> is-shorthand-for

> case L:
> case J:
> case K:
> X;
We also have the inverse problem, rehabilitating lambda syntax to be aligned 
with the switch syntax. 
The only discempancy i'm aware of is 
case Foo -> throw ... 
being allowed while 
(Foo foo) -> throw  
is not. 

BTW, this year i've presented the switch expression before the lambda, so a 
student ask me why Java does not allow colon in lambda, 
like this 
(a, b) : 
X 
break; 

instead of 
(a, b) -> { 
X 
} 

I answered explaining that a lambda was a function not a block of instructions, 
but I still feel a diffuse guilt about the reuse of -> inside the switch. 

Rémi 

> On 5/17/2021 5:36 PM, Brian Goetz wrote:

>> This is a good time to look at the progress we've made with switch. When we
>> started looking at extending switch to support pattern matching (four years
>> ago!) we identified a lot of challenges deriving from switch's C legacy, some
>> of which is summarized here:

>> [ http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html |
>> http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html ]

>> We had two primary driving goals for improving switch: switches as 
>> expressions,
>> and switches with patterns as labels. In turn, these pushed on a number of
>> other uncomfortable aspects of switch: fall through, totality, scoping, and
>> null handling.

>> Initially, we were unsure we would be able to rehabilitate switch to support
>> these new requirements without being forever bogged down by the mistakes of 
>> the
>> past. Bit by bit, we have chipped away at the negative aspects of switch, 
>> while
>> respecting the existing code that depends on those aspects. I think where 
>> we've
>> landed is, in many ways, better than we could have initially hoped for.

>> Throughout this exercise, there were periodic calls for "just toss it and 
>> invent
>> something new" (which we sometimes called "snitch", shorthand for "new
>> switch"*), and no shortage of people's attempts to design their ideal switch
>> construct. We resisted this line of attack, because we believed having two
>> similar-but-different constructs living side by side would be more annoying
>> (and confusing) to users than a rehabilitated, albeit more complex, 
>> construct.

>> The first round of improvements came with expression switches. This was the 
>> easy
>> batch, because it didn't materially change the set of questions we could ask
>> with switch, just the form in which we asked the question. This brought the
>> following improvements:

>> - Switches as expressions. Many existing switch statements are in reality
>> modeling expressions, in a more roundabout and less safe way. Expressing it
>> directly is simpler and less error-prone.
>> - Checked totality. The compiler enforces that a switch expression is 
>> exhaustive
>> (because, expressions must be total). In the case of enum switches, a switch
>> that covers all the cases needs no default clause, and the compiler inserts 
>> an
>> extra case to catch novel values and throw (ICCE) on them. (Eventually the 
>> same
>> will be true for switches on sealed classes as well.)
>> - A fallthrough-free option. Switches now give us a choice between two 
>> styles of
>> _switch blocks_, the old willy-nilly style, and the new single-consequent
>> (arrow) style. Switches that choose arrow-style need not reason about
>> fallthrough.

>> Unfortunately, it also brought a new asymmetry; switch expressions must be 
>> total
>> (and you get enhanced type checking for this), but switch statements cannot 
>> be.
>> This is a shame, since the improved type checking for totality is one of the
>> best things about the improvements in switch, as a switch that is total by
>> virtue of actually covering all the cases acts as a tripwire against new enum
>> constants / permitted subtypes being 

Re: Rehabilitating switch -- a scorecard

2021-05-18 Thread Remi Forax
- Mail original -
> De: "Brian Goetz" 
> À: "John Rose" 
> Cc: "amber-spec-experts" 
> Envoyé: Mardi 18 Mai 2021 04:37:55
> Objet: Re: Rehabilitating switch -- a scorecard

>> There are a few roads not taken:  “switch ()” with boolean
>> case expressions has not showed itself worthy yet.
> 
> Yep, this one can sit on the shelf.
> 
>> I’d also like to point out “switch (a, b)” as a possible area
>> of future work for switch, where the thing after “switch” is a
>> more generalized argument expression
> 
> ML supports this, because it just treats the operand as a tuple, and
> automatically destructures tuples.   The killer use case is, of course,
> FizzBuzz:
> 
>     switch (n % 3, n % 5) {
>     case (0, 0) -> "FizzBuzz";
>     case (0, _) -> "Fizz";
>     case (_, 0) -> "Buzz";
>     default -> n.toString();
>     }

with the destructuring pattern, our current version is

  record Result(int d3, int d5) {}
  switch(new Result(n % 3, n % 5)) {
case Result(var d3, var d5) && d3 == 0 && d5 == 0 -> "FizzBuzz";
case Result(var d3, var __) && d3 == 0 -> "Fizz";
case Result(var __, var d5) && d5 == 0 -> "Buzz";
default -> "" + n;
  }

to fill the gap, we need
 - _ as pattern equivalent to var _ + _ not be entered in the scope

 - constant as pattern, Foo(constant) being equivalent to Foo(var x) && x == 
constant

 - inference of Type in a destructuring pattern, case Foo(var x, var y) becomes 
case (var x, var y) if the type of the switched value is Foo
   This kind of inference is also useful in pattern assignment
 with Foo(var x, var y) = foo; being written (var x, var y) = foo;
   
 - tuple as first class citizen
   (expr, expr2) is equivalent to either creating a record + new 
SyntheticRecord(expr, expr2)  or new Tuple(expr, expr2)
   This feature is also useful to model methods that returns several 
values/deconstructor, but it means that
  either methods descriptor will contains synthetic record name which is 
ugly (TypeRestriction may help here),
  or we are able to cook a primitive parametrized/specialized class Tuple, 
the creation can be an indy converted as (default  + a sequence of withfields),
  but we have to wait Valhalla.

Rémi


Re: Rehabilitating switch -- a scorecard

2021-05-17 Thread Remi Forax
> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Lundi 17 Mai 2021 23:36:30
> Objet: Rehabilitating switch -- a scorecard

> This is a good time to look at the progress we've made with switch. When we
> started looking at extending switch to support pattern matching (four years
> ago!) we identified a lot of challenges deriving from switch's C legacy, some
> of which is summarized here:

> [ http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html |
> http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html ]

> We had two primary driving goals for improving switch: switches as 
> expressions,
> and switches with patterns as labels. In turn, these pushed on a number of
> other uncomfortable aspects of switch: fall through, totality, scoping, and
> null handling.

> Initially, we were unsure we would be able to rehabilitate switch to support
> these new requirements without being forever bogged down by the mistakes of 
> the
> past. Bit by bit, we have chipped away at the negative aspects of switch, 
> while
> respecting the existing code that depends on those aspects. I think where 
> we've
> landed is, in many ways, better than we could have initially hoped for.

> Throughout this exercise, there were periodic calls for "just toss it and 
> invent
> something new" (which we sometimes called "snitch", shorthand for "new
> switch"*), and no shortage of people's attempts to design their ideal switch
> construct. We resisted this line of attack, because we believed having two
> similar-but-different constructs living side by side would be more annoying
> (and confusing) to users than a rehabilitated, albeit more complex, construct.

> The first round of improvements came with expression switches. This was the 
> easy
> batch, because it didn't materially change the set of questions we could ask
> with switch, just the form in which we asked the question. This brought the
> following improvements:

> - Switches as expressions. Many existing switch statements are in reality
> modeling expressions, in a more roundabout and less safe way. Expressing it
> directly is simpler and less error-prone.
> - Checked totality. The compiler enforces that a switch expression is 
> exhaustive
> (because, expressions must be total). In the case of enum switches, a switch
> that covers all the cases needs no default clause, and the compiler inserts an
> extra case to catch novel values and throw (ICCE) on them. (Eventually the 
> same
> will be true for switches on sealed classes as well.)
> - A fallthrough-free option. Switches now give us a choice between two styles 
> of
> _switch blocks_, the old willy-nilly style, and the new single-consequent
> (arrow) style. Switches that choose arrow-style need not reason about
> fallthrough.

> Unfortunately, it also brought a new asymmetry; switch expressions must be 
> total
> (and you get enhanced type checking for this), but switch statements cannot 
> be.
> This is a shame, since the improved type checking for totality is one of the
> best things about the improvements in switch, as a switch that is total by
> virtue of actually covering all the cases acts as a tripwire against new enum
> constants / permitted subtypes being added later, rather than papering it over
> with a catch-all. We explored several ways to explicitly add back totality
> checking, but this always felt like a hack, and requires the programmer to
> remember to ask for this checking.

> Our resolution here offers a path to true healing with minimal user impact, by
> (temporarily) carving out the semantic space of old statement switches. A
> "legacy switch" is a statement switch on a numeric primitive or its box, enum,
> or string, and which contains no pattern labels (i.e., a statement switch that
> is valid today.) Like expression switches, we will require non-legacy 
> statement
> switches to be exhaustive, and warn on non-exhaustive legacy switches. (To 
> make
> the warning go away, just insert a "default: " or "default: break" at the
> bottom of the switch; not painful.) After some time, we should be able to make
> this warning an error, which again is easy to mitigate with a single line. In
> the end, all switch constructs will be total and type-checked for
> exhaustiveness, and once done, the notion of "legacy switch" can be
> garbage-collected.

> Looking ahead to patterns in switch, we have several legacy considerations to
> navigate:

> - Fallthrough and bindings. While fallthrough is not inherently problematic
> (though the choice of fallthrough-by-default was unfortunate), if a case label
> introduces a pattern variable, then fallthrough to another case (at least one
> that doesn't introduce the same pattern variable with the same type) makes
> little sense, and such fallthrough has been outlawed.
> - Scoping. The block of a switch is one big scope, rather than each case label
> group being its own scope. (Again, one might call this a historical error,
> since there's little 

Re: Switch labels (null again), some tweaking

2021-04-27 Thread Remi Forax
> De: "Maurizio Cimadamore" 
> À: "Brian Goetz" , "amber-spec-experts"
> 
> Envoyé: Mardi 27 Avril 2021 16:23:20
> Objet: Re: Switch labels (null again), some tweaking

> On 23/04/2021 16:38, Brian Goetz wrote:

>> So, I think the "a switch only accepts null if the letters n-u-l-l are 
>> present",
>> while a comforting move in the short term, buys us relatively little, and 
>> dulls
>> our pain receptors which in turn makes it take way longer to learn how 
>> patterns
>> really work. I think we should go back to:

>> - A switch accepts null if (a) one of the case labels is `null` or (b) the
>> switch has a total pattern (which must always be the last case.)
> The proposal seems ok; it does nothing for the problem I'm concerned about 
> (e.g.
> type of the target expression changing and influencing the totality analysis 
> at
> a distance) - but that was not address by the previous proposal either (as you
> say in your email, admittedly, I was reading too much into your proposal).
At least, if you want a total type, you can make it explicit using 
case var x 
which is always total whatever the type switched upon. 

So you have two tools to avoid influence at distance in the totality analysis, 
- for a switch where you can enumerate all the values enums, sealed type 
(boolean should be in that category too IMO, but we did not agree on that), if 
you list all the possible cases, a total case is not required. 
- use "var" so the total case is explicit, "case var x", "case Foo(var x)", etc 

> Stepping back - my general feeling on this topic is that the audience in this
> mailing list have a very intimate knowledge of what a "total pattern" is, to
> the point that they are comfortable building on top of this definition to e.g.
> define null behavior of patterns. I'm a little afraid that the finer detail of
> totality might be lost on the average Joe developer: totality is a much more
> slippery concept than it seems. Sure, there is one obvious way to make your
> pattern total: if the target expression has type E, then add a type test
> pattern whose type is also E. That seems easy enough. Except that, the type of
> E will not always be that manifest in user code (e.g. might be the result of
> what javac happens to infer on Tuesdays). And, if you mix this with sealed
> classes, it might be possible for a switch to go from total to non-total, as
> new permitted subtypes are added to a sealed hierarchy. These might all be all
> corner cases - but I think it's this complexity which contributes to my "isn't
> this all too subtle?" feeling.

> Obviously I'm well aware that nearly every path has been explored, and no 
> silver
> bullet seems to be laying around, so... this might just be the best we can
> offer, and that's ok.
We tried to have a way to signal that a pattern is total or not, by example, 
using a keyword, like total or default, but it did not work well when a pattern 
is partially total like "case Foo(true, var x)" 

> Cheers
> Maurizio
Rémi 


Re: Make all switches exhaustive?

2021-04-27 Thread Remi Forax
> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Mardi 27 Avril 2021 19:11:18
> Objet: Make all switches exhaustive?

> Maurizio's comment got me thinking about a possible way to eliminate the sharp
> edge of "sometimes switches are type-checked for exhaustiveness, and sometimes
> not."

> Historically, there is no requirement that switches be exhaustive; legacy
> switches without a default are like unbalanced ifs. (Notably, though, the DA
> rules do distinguish between switches with and without a default, and switches
> implicitly reject any null remainder -- so we still do have a solid base to
> build on here.)

> When we did expression switches, we said "expression switches must be total."
> This was a forced move if we wanted to reuse "switch", because expressions 
> must
> be total and compatibility backed us into letting statement switches be
> partial. Unfortunately, this leaves statement switches with less type checking
> than expression switches, since there is no way to even opt into 
> exhaustiveness
> checking.

> As switch gets more powerful (e.g., switching over sealed types with 
> patterns),
> this lack of exhaustiveness checking becomes a bigger deal. We'd been 
> imagining
> that we'd have to "patch" switch to allow opting into exhaustiveness checking,
> but maybe there's another way.

> As we are about to dramatically extend the power of switch (by permitting
> pattern labels, and allowing switches on other types than integers, boxes,
> strings, and enums), what we could do is:

> - Erroring on non-exhaustive non-legacy statement switches now;
> - Warning on non-exhaustive legacy statement switches now;
> - Later upgrading these warnings to errors, after a period of time where 
> people
> can update their code.

> The world we end up in in the long run is: all switches are exhaustive, with 
> no
> new language surface for managing exhaustiveness.
This will not solve the problem of Maurizio for sub-patterns but i like it. 

We don't have to later transform the warning to an error, keeping it as a 
warning can be annoying enough to force people to change their code (or 
ALT-ENTER + CTRL+1 ont it). 

> Legacy switches are those whose operand type is one of the "classic" types and
> all labels are constant labels or "default".

> For a switch that is deliberately non-exhaustive, all the user has to do to
> capture this (and shut up the compiler) is:

> default: break;
"default:" should be enough no ? 

> which is not very intrusive, and arguably makes the code more readable anyway.
> Users will see a speed bump when upgrading to pattern switches (clippy will
> tell them "I see you're writing a pattern switch, don't forget to end it with
> default:break") which presumably they will quickly internalize.

> (How's that for teaching an old dog new tricks?)
I think we should tackle "default" not being at the end the same way. 

Rémi 


Re: Switch labels (null again), some tweaking

2021-04-25 Thread Remi Forax
I just want to add that there is another possible semantics than having case 
null + total pattern as null acceptable pattern. 

We have based the semantics of pattern matching to the semantics of the cascade 
of if instanceof with an else at the end. 
There is another one, method calls [1], a switch like this 
switch(o) { 
case String s: 
case Object o: 
} 
can be seen as equivalent to the methods 
void m(String s) { ... 
void m(Object o) { ... 
with the switch being equivalent to a call to the method m. 

This semantics is equivalent to the cascade of instanceof apart in one 
scenario, when the value is null. 
A call to m(null) invokes m(String s), not m(Object). 

You may think that the method call semantics is not a good one because it can 
answer that no method are applicable or no method are more specific than the 
other, 
but those cases are not possible if the switch is total ... apart with null. 

If the value is null and we have a switch like this 
switch(o) { 
case String s: 
case Integer i: 
case Object o: 
} 
if we use the method call semantics, either "String s" or "Integer i" can be 
called, and given that none is more specific than the other. 

We can deviate from the original method call semantics by saying, let's take 
the first match, but in that case, it's just a different semantics, not an 
existing Java semantics. 

I don't like this semantics, because it will be surprising for anyone that null 
will be captured by the first case. I prefer null to be trapped by the total 
pattern at the end. 

Sadly, it means that there is no *obvious* semantics for null and that whatever 
we choose, it will not what some people expect. 

Rémi 

[1] https://docs.oracle.com/javase/specs/jls/se16/html/jls-15.html#jls-15.12 

> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Vendredi 23 Avril 2021 17:38:42
> Objet: Re: Switch labels (null again), some tweaking

> Gavin has a spec that captures most of what we've talked about here, which
> should be posted soon. But I want to revisit one thing, because I think we may
> have swung a little too eagerly at a bad pitch.

> There is a distinction between the _semantics of a given pattern_ and _what a
> given construct might do with that pattern_. The construct (instanceof, 
> switch)
> gets first crack at having an opinion, and then may choose to evaluate whether
> the pattern matches something. The place where we have been most tempted to 
> use
> this flexibility is with "null", because null ruins everything.

> We made a decision to lump pattern matching in with `instanceof` because it
> seemed silly to have two almost identical but subtly different constructs for
> "dynamic type test" and "pattern match" (given that you can do dynamic type
> tests with patterns.) We knew that this would have some uncomfortable
> consequences, and what we have tentatively decided to do is outlaw total
> patterns in instanceof, so that users are not confronted with the subtle
> difference between `x instanceof Object` and `x instanceof Object o`. This may
> not be a totally satisfying answer, and we left some room to adjust this, but
> its where we are.

> The fact is that the natural interpretation of a total type pattern is that it
> matches null. People don't like this, partially because it goes against the
> intuition that's been built up by instanceof and switch. (If we have to, I'm
> willing to lay out the argument again, but after having been through it 
> umpteen
> times, I don't see any of the alternatives as being better.)

> So, given that a total type pattern matches null, but legacy switches reject
> null...

> The treatment of the `null` label solves a few problems:

> - It lets people who want to treat null specially in switch do so, without
> having to do so outside the switch.
> - It lets us combine null handling with other things (case null, default:), 
> and
> plays nicely when those other things have bindings (case null, String s:).
> - It provides a visual cue to the reader that this switch is nullable.

> It is this last one that I think we may have over-rotated on. In the treatment
> we've been discussing, we said:

> - switch always throws on null, unless there's a null label

> Now, this is clearly appealing from a "how do I know if a switch throws NPE or
> not" perspective, so its understandable why this seemed a clever hack. But
> reading the responses again:

> Tagir:
> > I support making case null the only null-friendly pattern.

> Maurizio:
> > there's no subtle type dependency analysis which determines the fate of null

> it seems that people wanted to read way more into this than there was; they
> wanted this to be a statement about patterns, not about switch. I think this 
> is
> yet another example of the "I hate null so much I'm willing to take anything 
> to
> make it (appear to) go away" biases we all have." Only Remi seemed to have
> recognized this for the blatant trick it is -- we're hiding the null-accepting
> behavior of 

Re: Switch labels (null again), some tweaking

2021-04-23 Thread Remi Forax
- Mail original -
> De: "John Rose" 
> À: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Envoyé: Vendredi 23 Avril 2021 21:27:40
> Objet: Re: Switch labels (null again), some tweaking

> On Apr 23, 2021, at 8:38 AM, Brian Goetz  wrote:
>> 
>> ...The fact is that the natural interpretation of a total type pattern is 
>> that
>> it matches null.  People don't like this…
> 
> This person does.  As you point out, this position
> enables composition of patterns.  And composition
> is the more important goal, compared to preservation
> of intuitions about edge cases.

I agree composition is more important, a total pattern should match null 
because semantically a total pattern is equivalent to "else" and not "if 
instanceof".

[...]

>> Now, let's look back at the alternative, where we keep the flexibility of the
>> null label, but treat patterns as meaning what they mean, and letting switch
>> decide to throw based on whether there is a nullable pattern or not.  So a
>> switch with a total type pattern -- that is, `var x` or `Object x` -- will
>> accept null, and thread it into the total case (which also must be the last
>> case.)
> 
> To me this is the material point, and has been all along:
> There is never a need to perform an O(N) visual scan of
> a switch to see if it accepts nulls, since the users simply
> have to inspect the final (and perhaps initial) case of the
> the switch.  Good style will avoid puzzlers such as final
> cases which are difficult to classify (total vs. partial).
> The language does not have to carry the burden of
> enforcing full annotation.
> 
> A second point:  In the present design, and especially
> with the cleanup you are proposing, I think there are
> several possible (optionally selectable) styles of
> null processing in switches.
> 
> (Reading later I see you already brought up most
> of these points.)
> 
> Style A1 is implicit null acceptance.  Pattern
> totality determines null acceptance, end of story.
> Style A2 is implicit null rejection, with markup
> of non-totality using `default`.  That is, a coder
> could choose, as a matter of clarity, to call out
> partial final cases by adding `default: break;`
> after them.  (E-switches don’t need A2.)
> 
> Style B1 is explicit null acceptance.  If null
> is allowed by the switch, then the token
> `null` must be present, either at the head
> of the switch or just before the final total
> pattern.  Style B2 adds explicit null rejection,
> by adding something like `case null: throw …`.
> That’s probably too much noise in most cases.
> 
> The current draft mandates explicit null
> acceptance.  Brian is suggesting that we
> allow the other style too.  I think it’s a
> good suggestion.
> 
> (An extra option would be to allow some
> bespoke way to annotate totality and/or
> non-totality of the final case. 

If we have a change to do, i'm not sure, i would prefer that option.
Make clear that the last pattern is total so it matches null.

> That interacts with things like enums and sealed classes,
> so I’ll just mention it and move on.)

???,
in those cases, no total pattern is needed, i don't see the interaction.

> 
> I think that IDEs can help users pick
> the style that is correct for them.
> 
> Bottom line:  Trust the users to choose how
> explicit to be with nulls.  More importantly,
> trust them with compositional notations.

That's a solution, we do nothing and trust users.
The other solution is to force users to explicit say that the pattern is total, 
by example by asking to use 'var o' instead of 'Object o',
but we already discuss that and only me and Tagir were agreeing that it is a 
good idea.

[...]

> 
> — John

Rémi


Re: Possible refinement for sealed classes

2021-04-01 Thread Remi Forax
> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Jeudi 1 Avril 2021 15:23:28
> Objet: Possible refinement for sealed classes

> Been working with sealed classes a lot lately, and have run into a pattern 
> that
> we might want to consider supporting more directly, which is interfaces that
> exist solely to refine a sealed class or interface. Suppose we have:

> sealed abstract class Sandwich { }

> interface WithCheese { }
> interface Toasted { }
> interface NontraditionalStructure { }
> interface NontraditionalIngredients { }

> class Cheeseburger extends Sandwich implements WithCheese { }

> class TunaMelt extends Sandwich implements Toasted, WithCheese { }

> class Burrito extends Sandwich implements WithCheese, NontraditionalStructure 
> {
> }

> class ChipButty extends Sandwich implements NontraditionalIngredients { }

> class PopTart extends Sandwich implements NontraditionalIngredients,
> NonTraditionalStructure { }

> (see, e.g., [ https://twitter.com/matttomic/status/859117370455060481 |
> https://twitter.com/matttomic/status/859117370455060481 ] , for those who 
> don't
> get the joke.)

> The constraint we would like to express here is that the interfaces WithCheese
> and Toasted exist to serve Sandwich, which is extension-controlled. To capture
> this, we also seal these interfaces, but now have to redundantly enumerate
> their subtypes. This is annoying and brittle, because we're stating it
> indirectly; a more direct capture of intent would be to say "Toasted can only
> be used with Sandwich."

> Even if Sandwich were an interface, and then we can say "Toasted extends
> Sandwich", we still have to redundantly enumerate the subtypes. If this game 
> is
> about Sandwich, just having Sandwich enumerate its subtypes should be enough.

> Without trying to paint the bikeshed, this is a pretty simple extension to
> sealing:

> sealed interface WithCheese __refines Sandwich { }

> This says that WithCheese is sealed to either classes that extend Sandwich, or
> other interfaces that __refines Sandwich. As a bonus, default methods in
> WithCheese can refer to methods in Sandwich, since it must always be the case
> that `this instanceof Sandwich`.

> Essentially, I want to put the `permits` list on Sandwich, and have 
> `WithCheese`
> and friends delegate their permits to `Sandwich`, rather than having each
> interface enumerate their subtypes.
I'm not sure to understand why this case is special enough to get a special way 
of delegating the "sealness" (the fact to be sealed, i don't know if there is a 
term in English for that) apart that it would be cool to have it. 

Delegating the "sealness" in that direction, from an interface which subtypes 
are subtypes to the sealed type, seems less readable that the other direction, 
from the sealed type to the interface that will have subtypes that implement 
the sealed type. 
So is it equivalent to 
sealed interface Sandwich permits __relaxed WithCheese, WithToast, etc 
with __relaxed meaning that WithCheese doesn't have to be a subtype of 
Sandwich, but the subtypes of WithCheese, etc have to ? 

If Sandwich see WithCheese but not the subtypes of WitchCheese, it can still be 
sealed with those types ? 

How it works in term of separate compilation, if I add "__refines Sandwich" to 
a WithCheese without recompiling Sandwich ? 

Rémi 


Re: Draft JEP: Sealed Classes

2021-03-22 Thread Remi Forax
> De: "Gavin Bierman" 
> À: "amber-spec-experts" 
> Envoyé: Lundi 22 Mars 2021 13:03:31
> Objet: Draft JEP: Sealed Classes

> Dear all,

> Now JDK 16 is out (yay!) it’s time for us to focus on finalizing Sealed 
> Classes
> in JDK 17.

> I have written a draft JEP, which is unchanged (other than some superficial
> editorial changes) from JEP 397.

> [ https://openjdk.java.net/jeps/8260514 | 
> https://openjdk.java.net/jeps/8260514
> ]

> (I think Brian’s proposal to consider extending the language in some way to
> support assignment and switch statements being total over sealed hierarchies 
> is
> something that can be considered as a JEP in itself.)

> If you have any comments on the draft JEP, please let me know!

I think it's missing a discussion about lambdas, anonymous classes and local 
classes that can all extends/implements a sealed type. 
For Lambdas and anonymous classes, it's easy, they are anonymous, so have no 
name to list in the permits clause. 

For Local classes, they are not allowed because they may not be visible 
sealed interface I {} // I don't see A from here 
void foo() { 
record A() implements I {} 
} 

But i think we may want to relax that rule a little to be able to declare a 
sealed type and an interface if they are at the same "nested level" 
@Test 
void aTestMethod() { 
sealed interface I {} 
record A() implements I {} 
} 

It's very convenient when you want different hierarchies when testing things 
like reflection inside JUnit. 

I also think we should add a discussion about why using the keyword 
"non-sealed" instead of something like "open", i.e why using a hyphen separated 
keyword instead of a "grammar local" keyword, 
because it's a question often asked when i've explained that JEP. Something 
along the line that hyphen separated keywords are a lexer issue so it's less 
work for all the Java ecosystem, all the tools that parse java code, than a 
using "grammar local" keyword which requires to tweak the parser or the grammar 
(or both). 

And some minor remarks, 
in the JEP, the example 
abstract sealed class Shape { 
class Circle extends Shape { ... } 
... 
} 
The class Circle should be declared static if we want to have the same behavior 
than the example above in the JEP. 

Also, getPermittedSubClasses() returns a java.lang.Class not a 
java.lang.Class ( is missing). 

> Thanks,
> Gavin

regards, 
Rémi 


Type pattern and raw type

2021-03-15 Thread Remi Forax
Hi, currently when there is an instanceof + type pattern with a raw type,
javec doesn't raise any warning (oops), worst it compiles because i believe it 
should not, but for that, the spec has to be amended.

  interface Foo {
void set(T t);
  }

  class FooImpl implements Foo {
private String value;

@Override
public void set(String s) {
  value = s;
}
  }

  public static void main(String[] args) {
Object o = new FooImpl();
if (o instanceof Foo foo) {
  foo.set(3);
}
  }

The JLS 14.30.2 says
"An expression, e, that is not the null literal is compatible with a type test 
pattern of type T if e can be converted to type T by a casting conversion 
(5.5), and the casting conversion does not make use of a narrowing reference 
conversion which is unchecked (5.1.6.2)."

Unchecked conversions are forbidden because of type pollution, that why you can 
not write
  if (o instanceof Foo foo) {

Raw type conversions also leads to type pollution, so i think that a type 
pattern with a raw type should be forbidden too.

So at least, javac should raise a warning and IMO the spec should be amended to 
fordid raw types too.

regards,
Rémi


Re: [External] : Re: Looking ahead: pattern assignment

2021-03-13 Thread Remi Forax





De: "Brian Goetz"  
À: "Remi Forax"  
Cc: "amber-spec-experts"  
Envoyé: Samedi 13 Mars 2021 02:41:58 
Objet: Re: [External] : Re: Looking ahead: pattern assignment 




BQ_BEGIN


BQ_BEGIN


BQ_BEGIN
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.) 
BQ_END


More questions, 
About the conversions, you means the conversions with the top-level type of the 
pattern, or conversions with the sub-patterns too ? 

BQ_END

What I mean is that if I have a method 

void m(Integer x) { } 

and I call m(1), then today we do an boxing conversion because boxing is one of 
the conversions we do in method invocation context. If we want to interpret 
this as a pattern match, we should get the same answer, which means we need to 
define what conversions we apply to the parameter when the parameter pattern 
has a certain target type, and that has to align with existing invocation 
context conversions. 
BQ_END


What is not clear to me is are you proposing those conversion to be applied on 
the target type of the pattern or even on the target type of the sub patterns. 

But perhaps it's a more general question for the deconstruction pattern, if we 
do not use 'var' 
case Point(Type1 x, Type2 y): ... 
what are the possible types for Type1 and Type2 given that Point is declared as 
Point(DeclaredType1 x, DeclaredType2 y). 
Do Type1 has to be DeclaredType1 (resp. Type2 == DeclaredType2) or do some 
conversions are allowed ? 


BQ_BEGIN



BQ_BEGIN

For lambda formals, there is a syntax question, do we support a syntax without 
parenthesis if there are only one argument ? 
Point(var x, var y) -> ... 

BQ_END

No for now; ask me again in two years ;) 


BQ_BEGIN

For methods and lambda formals, does the top-level pattern as to have a binding 
? 
Like in your examples 
void m(Point(var x, var y) p) { ... } 
or can we avoid it 
void m(Point(var x, var y)) { ... } 

BQ_END

Today, yes, because method parameters need names. Tomorrow, when we allow 
method parameters to be elided via _, we might consider allowing omitting the 
parameter binding, but we might also just require it to be _. 
BQ_END

Given that you can get the parameter names by reflection, requiring '_' may not 
be a bad idea. 
This lead to another question, should we consider using a Pattern declared 
inside a method declaration only as syntactic sugar or should the notation 
reflected in an attribute of the method accessible by reflection ? 


BQ_BEGIN



BQ_BEGIN

For classical assignment, enhanced for loop, try-with-resources and lambda 
formals we can have inference, do we have inference with patterns ? 
- assignment 
(var x, var y) = aPoint; 

BQ_END

That's not a pattern :) 

Point(var x, var y) = aPoint 

is a pattern. In this case, we can infer what type x and y have based on the 
declaration of the selected deconstructor. 
BQ_END

It's an alternative syntax to the deconstruction pattern, usual deconstruction 
pattern is 
Type(pattern1, pattern2, ...) 
The alternative syntax is 
(pattern1, pattern2, ...) 
and the type is found by type inference. 

This may be important to explore that now because the syntax may clash with the 
parenthesized pattern because 
(pattern) 
can be seen as both. 


BQ_BEGIN



BQ_BEGIN

- enhanced loop 
for((var x, var y) : aListOfPoints) { ... } 
- try-with-resources 
try((var x, var y): aClosablePoint) { ... } 
- lambdas formals 
Consumer consumer = ((var x, var y)) -> ... 

BQ_END

Same "not a pattern" comment. 


BQ_BEGIN

Also should we support the parenthesized pattern and the AND pattern in those 
contexts ? 

BQ_END

The key criteria is totality. In practicality that means unguarded type and 
deconstruction patterns. 
BQ_END

And the pattern method if we allow some pattern methods to be declared as 
total. 

Rémi 



Re: Looking ahead: pattern assignment

2021-03-12 Thread Remi Forax
> De: "Alan Malloy" 
> À: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Envoyé: Vendredi 12 Mars 2021 22:58:44
> Objet: Re: Looking ahead: pattern assignment

> try-with-resources is a little more subtle than the other contexts. Suppose 
> that
> I write:
> try (Foo(Bar x) = ...) {
> ...
> }

> What should be closed in the finally? The variable x that we bound to, or the
> Foo that contained it?

The instance of Foo, not the instance of Bar. 

> Both answers seem a little weird to me. Might it be better not to allow 
> patterns
> in this context ?

It can be handy if we have a pattern method that decomposes the Closeable, by 
example we have a pattern method HttpRequest.parse() that returns the header 
and the content of the HTTP request 

try (HttpRequest.parse(var header, var content) = getAHttpRequestFrom(...)) { 
... 
} 

regards, 
Rémi 

> On Fri, Mar 12, 2021 at 12:41 PM Brian Goetz < [ 
> mailto:brian.go...@oracle.com |
> brian.go...@oracle.com ] > 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.

>> 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) -> { ... }

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

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

>> The remaining case where this construct helps is when we want to assign 
>> default
>> values:

>> let Point(var x, var y) = aPoint
>> else { x = y = 0; }
>> // can use x, y here either way

>> But, I think we can get there another way, by letting patterns bind to 
>> existing
>> variables somehow (we want something like this for the analogue of
>> super-delegation and similar in pattern declarations anyway.) I won't paint
>> that bikeshed here, except to suggest that the let-else construct seems to 
>> be a
>> losing price-performance proposition.

>> I suspect the right time to formalize pattern assignment is when we formalize
>> deconstructor declarations (probably next round). In the meantime, we should:

>> - gather a complete list of contexts where pattern assignment makes sense;
>> - nail down semantics of primitive type patterns (see earlier mail);
>> - think about how to align the conversion rules in JLS 5 to align with 
>> existing
>> usage.

>> *the only remaining difference between pattern variables and locals is that
>> pattern variables have a more interestingly-shaped scope (and perhaps in the
>> future, pattern variables may have multiple declaration points in the 
>> presence
>> of OR patterns / merging via ORing of boolean expressions)


Re: Looking ahead: pattern assignment

2021-03-12 Thread Remi Forax
> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Vendredi 12 Mars 2021 20:58:40
> Objet: Looking ahead: pattern assignment

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

> 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.
yes, with remainder please :) 
NPE if the expression is null, ICCE if the number/type of the bindings are 
incompatible. 

> 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) -> { ... }

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

> 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.)
More questions, 
About the conversions, you means the conversions with the top-level type of the 
pattern, or conversions with the sub-patterns too ? 

For lambda formals, there is a syntax question, do we support a syntax without 
parenthesis if there are only one argument ? 
Point(var x, var y) -> ... 

For methods and lambda formals, does the top-level pattern as to have a binding 
? 
Like in your examples 
void m(Point(var x, var y) p) { ... } 
or can we avoid it 
void m(Point(var x, var y)) { ... } 

For classical assignment, enhanced for loop, try-with-resources and lambda 
formals we can have inference, do we have inference with patterns ? 
- assignment 
(var x, var y) = aPoint; 
- enhanced loop 
for((var x, var y) : aListOfPoints) { ... } 
- try-with-resources 
try((var x, var y): aClosablePoint) { ... } 
- lambdas formals 
Consumer consumer = ((var x, var y)) -> ... 

Also should we support the parenthesized pattern and the AND pattern in those 
contexts ? 
- the parenthesized pattern may makes the grammar ambiguous. 
- do we allow something like 
Point(var x, _) & Point(_, var y) = aPoint; 

[...] 

> I suspect the right time to formalize pattern assignment is when we formalize
> deconstructor declarations (probably next round). In the meantime, we should:

> - gather a complete list of contexts where pattern assignment makes sense;
> - nail down semantics of primitive type patterns (see earlier mail);
> - think about how to align the conversion rules in JLS 5 to align with 
> existing
> usage.

> *the only remaining difference between pattern variables and locals is that
> pattern variables have a more interestingly-shaped scope (and perhaps in the
> future, pattern variables may have multiple declaration points in the presence
> of OR patterns / merging via ORing of boolean expressions)
Rémi 


Re: Guards redux

2021-03-12 Thread Remi Forax
- Mail original -
> De: "Gavin Bierman" 
> À: "John Rose" 
> Cc: "amber-spec-experts" 
> Envoyé: Vendredi 12 Mars 2021 11:11:46
> Objet: Re: Guards redux

> Thanks everyone for the discussion. I have updated the JEP:
> 
> http://openjdk.java.net/jeps/8213076
> 
> It proposes *guarded patterns* of the form `p&`, as well as parenthesized
> patterns, written `(p)`.


Hi Gavin,
nice, i find this version more appealing.

In the first example that shows && as a guard, the example uses parenthesis 
which is my opinion should be removed
case Triangle t && (t.calculateArea() > 100) -> 
System.out.println("Large Triangle"); 

if i read the grammar correctly, those parenthesis around the boolean 
expression are unnecessary
case Triangle t && t.calculateArea() > 100 -> 

it will be more inline with the example above in the text that is using 'when'
case Triangle t when t.calculateArea() > 100 ->


Otherwise, in the document, the words "->-style" and ":-style" are hard to 
parse, i think array-style and colon-style are better terms.


In the section "Dominance of pattern label",
It's not clear to me if there is a dominance in between expressions,
by example, can we have the following cases in that order ?

  case Foo(var a, var b) && a == 3:
  case Foo(var a, var b) && a == 3 && b  == 4:

for me the answer is yes, the dominance is computed on patterns only, not on 
expressions.


> 
> I have left out AND and OR patterns, at least for now. Thanks to Guy we now 
> know
> how to add them elegantly to the grammar when the time comes :-) When people
> come to play with this, I’d be especially interested to hear about the need 
> for
> AND and OR patterns. (I have a feeling that OR is more important, but that’s
> another email...)
> 
> Comments on the JEP very welcome.
> 
> Thanks,
> Gavin

regards,
Rémi

> 
> 
>> On 11 Mar 2021, at 13:51, Gavin Bierman  wrote:
>> 
>> Very clever, John. Yes, let’s not add any more puzzlers to the language!
>> 
>>> On 10 Mar 2021, at 21:59, John Rose  wrote:
>>> 
>>> Good.  PrimaryPattern is a good concept.
>>> 
>>> On Mar 10, 2021, at 6:47 AM, Gavin Bierman  wrote:
 
 Guard:
 : AssignmentExpression
>>> 
>>> I think it should be this instead:
>>> 
>>> Guard:
>>> : ConditionalAndExpression
>>> 
>>> The effects of this narrower definition of a guard expression
>>> are two:
>>> 
>>> First, any guard of the form (a || b), (a ? b : c), or (a = b)
>>> will require parentheses.
>>> 
>>> Second, as a result, the following puzzlers will not be legal
>>> inputs:
>>> 
>>> case P && a || b:  // compare x instanceof P && a || b
>>> case P && a ? b : c:  // compare x instanceof P && a ? b : c
>>> case P && a = b:  // compare x instanceof P && a = b
>>> 
>>> In all of these puzzlers, the “extremely fortuitous”
>>> correspondence between the syntaxes of guarded
>>> cases and instanceof’s with following boolean logic
>>> are broken.
>>> 
>>> The fix to align the meanings of the cases with the
>>> instanceof’s is to add parentheses:
>>> 
>>> case P && (a || b):  // compare x instanceof P && (a || b)
>>> case P && (a ? b : c):  // compare x instanceof P && (a ? b : c)
>>> case P && (a = b):  // compare x instanceof P && (a = b)
>>> 
>>> Using the modified grammar rule above forces the
>>> coder to write the parentheses, I think.
>>> 
>>> I think we should aim for “perfectly fortuitous”
>>> here, not just “well, look how often the syntaxes
>>> seem to mean the same thing although sometimes
>>> they don’t”.  Indeed, this is my main reason for
>>> plumping  for P && g in the first place.
>>> 
>>> — John
>>> 
>>> 


Re: Guards redux

2021-03-10 Thread Remi Forax
> De: "Brian Goetz" 
> À: "Gavin Bierman" , "amber-spec-experts"
> 
> Envoyé: Mercredi 10 Mars 2021 16:02:10
> Objet: Re: Guards redux

> This feels like we landed in a good place. It preserves the underlying goal of
> the original approach -- that we can compose patterns with patterns, and
> patterns with boolean expressions, and doesn't require nailing bags onto 
> switch
> (yay). The main difference is that it creates a distinguished "guarded 
> pattern"
> operator rather than asking users to compose guarded patterns from an
> expression-to-pattern operator and pattern-AND.

You nail the guard to a pattern, which is equivalent until we have nested 
patterns (and "or"/"and" patterns). 

I see a lot of advantages of using && to link a guard to a pattern, 
- the symbol is heavy so there is a clear visual separation 
- without any supplementary parenthesis, && after the type pattern in an 
instanceofis the && between expression, it's almost like you can not have a 
guard with an instanceof, in practice, few instanceof will have a guard. 

I still think that using a guard inside a nested pattern is ugly but it can be 
just that, ugly. Someone may want a short-circuit in a deeply nested patterns . 

> The main objections to the `P & true(e)` approach were the aesthetic reaction 
> to
> this use of `true` (which was one of those first-five-minutes reactions, and
> people might well have gotten over it in the next five minutes), and the more
> serious discoverability problem of having to compose two (currently 
> unfamiliar)
> features to get guarded patterns. The current proposal seems a lower energy
> state, while deriving from the same basic principles.

> FTR, the key observation that broke the jam was the observation that, if we
> treat & and && as:

> (&) :: Pattern -> Pattern -> Pattern
> (&&) :: Pattern -> Expression -> Pattern

> we can, with parentheses, achieve arbitrary orderings of patterns and guard
> expressions, and are not forced to push all guards to the end (which was where
> we got stuck the last time we looked at &&.)
As i said to Gavin, i'm not at ease with using the symbol '&' in between 
patterns. 

Rémi 

> On 3/10/2021 9:47 AM, Gavin Bierman wrote:

>> Okay, so it seems like our initial stab at guards and a pattern conjunction
>> operator needs some finessing.

>> Here's another take, inspired by Guy's emails.

>> Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean
>> expression e.

>> Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and
>> disjunction operators on patterns.

>> There are a couple of immediate parsing puzzlers:

>> * e instanceof String s && s.length() > 2

>> This parses as `(e instanceof String s) && s.length() > 2` today. We need to 
>> be
>> careful that our grammar continues to make this the case (see below). We will
>> also introduce a parenthesized pattern, which you can use if you want the
>> dangling `&& s.length() > 2` to parse as a guard for the `String s` type
>> pattern. (It is extremely fortuitous that the functional behavior of both
>> expressions is the same, but either way I think this is a simple rule.)

>> * case p && b -> c -> b

>> Now we have some ambiguity from the `->` in a lambda expression and in a 
>> switch
>> rule. Fortunately, again I think we can just lean into the grammar to get 
>> what
>> we want. At the moment, the grammar for expressions is:

>> Expression:
>> LambdaExpression
>> AssignmentExpression

>> As a lambda expression can never be a boolean expression it can never
>> meaningfully serve as a guard for a pattern. Great!

>> So, I'd like to suggest this grammar for patterns (including pattern 
>> conjunction
>> and pattern disjunction operators for completeness but we can drop them from 
>> the
>> first release):

>> Pattern:
>> : ConditionalOrPattern
>> : ConditionalOrPattern `&&` Guard

>> ConditionalOrPattern:
>> : ConditionalAndPattern
>> : ConditionalOrPattern `|` ConditionalAndPattern

>> ConditionalAndPattern:
>> : PrimaryPattern
>> : ConditionalAndPattern `&` PrimaryPattern

>> PrimaryPattern:
>> : TypePattern
>> : RecordPattern
>> : ArrayPattern
>> : `(` Pattern `)`

>> Guard:
>> : AssignmentExpression

>> Along with the following change to the grammar for instanceof:

>> InstanceofExpression:
>> : RelationalExpression `instanceof` ReferenceType
>> : RelationalExpression `instanceof` PrimaryPattern  <-- Note!

>> Some consequences:

>> p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay!

>> p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse
>> properly. But that's okay, as I think the second is much clearer.

>> Let me know what you think.

>> Gavin


Re: Guards redux

2021-03-10 Thread Remi Forax
- Mail original -
> De: "Gavin Bierman" 
> À: "amber-spec-experts" 
> Envoyé: Mercredi 10 Mars 2021 15:47:30
> Objet: Guards redux

> Okay, so it seems like our initial stab at guards and a pattern conjunction
> operator needs some finessing.
> 
> Here's another take, inspired by Guy's emails.
> 
> Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean
> expression e.
> 
> Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and
> disjunction operators on patterns.

Using & between patterns is unfortunate given that if if first pattern is not 
total and does not match, we will not "execute" the second pattern.
So the semantics is lazy but currently '&' means non-lazy on expressions.

> 
> There are a couple of immediate parsing puzzlers:
> 
> * e instanceof String s && s.length() > 2
> 
> This parses as `(e instanceof String s) && s.length() > 2` today. We need to 
> be
> careful that our grammar continues to make this the case (see below). We will
> also introduce a parenthesized pattern, which you can use if you want the
> dangling `&& s.length() > 2` to parse as a guard for the `String s` type
> pattern. (It is extremely fortuitous that the functional behavior of both
> expressions is the same, but either way I think this is a simple rule.)
> 
> * case p && b -> c -> b
> 
> Now we have some ambiguity from the `->` in a lambda expression and in a 
> switch
> rule. Fortunately, again I think we can just lean into the grammar to get what
> we want. At the moment, the grammar for expressions is:
> 
>Expression:
>LambdaExpression
>AssignmentExpression
> 
> As a lambda expression can never be a boolean expression it can never
> meaningfully serve as a guard for a pattern. Great!
> 
> So, I'd like to suggest this grammar for patterns (including pattern 
> conjunction
> and pattern disjunction operators for completeness but we can drop them from 
> the
> first release):
> 
> Pattern:
> : ConditionalOrPattern
> : ConditionalOrPattern `&&` Guard
> 
> ConditionalOrPattern:
> : ConditionalAndPattern
> : ConditionalOrPattern `|` ConditionalAndPattern
> 
> ConditionalAndPattern:
> : PrimaryPattern
> : ConditionalAndPattern `&` PrimaryPattern
> 
> PrimaryPattern:
> : TypePattern
> : RecordPattern
> : ArrayPattern
> : `(` Pattern `)`
> 
> Guard:
> : AssignmentExpression
> 
> Along with the following change to the grammar for instanceof:
> 
> InstanceofExpression:
> : RelationalExpression `instanceof` ReferenceType
> : RelationalExpression `instanceof` PrimaryPattern  <-- Note!
> 
> Some consequences:
> 
> p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay!
> 
> p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse
> properly. But that's okay, as I think the second is much clearer.
> 
> Let me know what you think.
> 
> Gavin


Re: Guards

2021-03-09 Thread Remi Forax
Apart from what have said about letting grobble to fully access to the 
bindings, 
i think that using parenthesis to call grobble here is already too much. 

case Foo(var x) when x > y 
is far more readable than 
case Foo(var x) when (x > y) 
because for a pattern the parenthesis describes a notion of ownership, you are 
describing the content of the type using a destructuring pattern, while the 
second pair of parenthesis is a kind of method call. 

Having two pairs of parenthesis with two complete different meanings is 
something is hope you can avoid, at least for the simple cases. 

Rémi 

> De: "Brian Goetz" 
> À: "amber-spec-experts" 
> Envoyé: Vendredi 5 Mars 2021 20:14:01
> Objet: Guards

> Let me try and summarize all that has been said on the Guards topic.

>  Background and requirements

> For `instanceof`, we don't need any sort of guard right now (with the patterns
> we have); we can already conjoin arbitrary boolean expressions with `&&` in 
> all
> the contexts we can use `instanceof`, because it's a boolean expression. (This
> may change in the future as patterns get richer.) So we can already express 
> our
> canonical guarded Point example with

> if (p instanceof Point(var x, var y) && x > y) { ... }

> with code that no one will find confusing.

> For switch, we can't do this, because case labels are not boolean expressions,
> they're some ad-hoc sub-language. When the sub-language was so limited that it
> could only express int and string constants, this wasn't a problem; there was
> little refinement needed on `case "Foo"`.

> As we make switch more powerful, we face a problem: if the user drifts out of
> the territory of what can be expressed as case labels, they fall off the cliff
> and have to refactor their 50-way switch into an if-else chain. This will be a
> really bad user experience. Some sort of escape hatch to boolean logic buys us
> insurance against this bad experience -- as long as you can express your
> non-pattern criteria with a boolean expression (which is pretty rich), you
> don't have to leave switch-land.

> So we took as our requirement:

> Some sort of guard construct that is usable in switch is a forced move.

>  Expressing guards in switch

> There are several ways to envision guards:

> - As patterns that refine other patterns (e.g., a "true" pattern)
> - As an additional feature of "case" in switch (e.g., a "when" clause)
> - As an imperative control-flow statement usable in "switch" (e.g., 
> "continue")

> We've largely rejected the third (even though it is more primitive than the
> others), because we think the resulting code will be much harder to read and
> more error-prone. We've bounced back and forth between "let's nail something 
> on
> the side of switch" and "let's let the rising pattern tide lift all 
> conditional
> constructs."

> Other languages have demonstrated that guards in switch-like constructs are
> viable.

> The argument in favor of nailing something on the side of switch is that it is
> pragmatic; it is immediately understandable, it raises the expressivity of
> `switch` to where `if` already is, and it solves the immediate requirement we
> have in adding patterns to switch.

> The argument against is that it is not a primitive; it is dominated by the
> option of making patterns richer (by adding boolean patterns), it is weak and
> non-compositional, and overly specific to switch. (It is possible to make
> worse-is-better arguments here that we should do this anyway, but it's not
> really possible to seriously claim better, despite attempts to the contrary.)

>  Interpreting the feedback

> The JEP proposes a powerful and compositional approach:

> - true/false patterns that accept arbitrary boolean expressions (and which
> ignore their target);
> - combining patterns with a pattern-AND combinator

> On the one hand, this is a principled, orthogonal, compositional, expressive,
> broadly applicable approach, based on sensible primitives, which will be 
> usable
> in other contexts, and which anticipate future requirements and directions.

> On the other hand, there has been a pretty powerful emotional reaction, which
> could be summarized as "sorry, we're not ready for this degree of generality
> yet with respect to patterns." This emotional reaction seems to have two
> primary components:

> - A "who moved my cheese" reaction to the overloading of `true` in this way --
> that `true` seems to be, in everyone's mind, a constant, and seeing it as a
> pattern is at least temporarily jarring. (This may be a temporary reaction, 
> but
> there's still a cost of burning through it.)

> - A reaction to "borrowing & from the future" -- because the other use cases 
> for
> &-composition are not obvious or comfortable yet, the use of &-composition
> seems foreign and forced, and accordingly engenders a strong reaction.

> The former (which I think is felt more acutely) could be addressed by taking a
> conditional keyword such as 

Re: Two new draft pattern matching JEPs

2021-03-03 Thread Remi Forax
- Mail original -
> De: "Gavin Bierman" 
> À: "amber-spec-experts" 
> Envoyé: Jeudi 18 Février 2021 13:33:20
> Objet: Two new draft pattern matching JEPs

> Dear all,
> 

[...]

> 
> - Pattern Matching for switch: 
> https://bugs.openjdk.java.net/browse/JDK-8213076
> 
> We split them up to try to keep the complexity down, but we might decide to
> merge them into a single JEP. Let me know what you think.

I think that we have got a little over our head with the idea of replacing the 
switch guard by the guard pattern + conditional-and pattern.

The draft is poor in explanations on why we should do that apart because it's 
more powerful, which is true but that not how to evaluate a feature.
Here, it doesn't seem we are trying to fix a broken feature or adapt an 
existing feature to Java. It's just more powerful, but with a lot of drawbacks, 
see below.

My main concern is when mixing the deconstructing pattern with the guard + and 
pattern, those twos (two and a half) doesn't mix well.

For a starter, at high level, the idea is to mix patterns and expressions 
(guards are boolean expressions), but at the same time, we have discussed 
several times to not allow constants inside patterns to make a clear 
distinction between patterns and expressions. We have a inconsistency here.

The traditional approach for guards cleanly separate the pattern part from the 
expression part
  case Rectangle(Point x, Point y) if x > 0 && y > 0
which makes far more sense IMO.

The current proposal allows
  case Rectangle(Point x & true(x > 0), Point y & true(y > 0))
which is IMO far least readable because the clean separation between the 
patterns and the expressions is missing.

There is also a mismatch in term of evaluation, an expression is evaluated from 
left to right, for a pattern, you have bindings and bindings are all populated 
at the same time by a deconstructor, this may cause issue, by example, this is 
legal in term of execution
  case Rectangle(Point x & true(x > 0 && y > 0), Point y)
because at the point where the pattern true(...) is evaluated, the Rectangle 
has already been destructured, obviously, we can ban this kind of patterns to 
try to conserve the left to right evaluation but the it will still leak in a 
debugger, you have access to the value of 'y' before the expression inside 
true() is called.

In term of syntax, currently the parenthesis '(' and ')' are used to 
define/destructure the inside, either by a deconstructor or by a named pattern, 
but in both cases the idea is that it's describe the inside. Here, true() and 
false() doesn't follow that idea, there are escape mode to switch from the 
pattern world into the expression world.
At least we can use different characters for that.

Also in term of syntax again, introducing '&' in between patterns overloads the 
operator '&' with one another meaning, my students already have troubles to 
make the distinction between & and && in expressions. 
As i already said earlier, and this is also said in the Python design document, 
we don't really need an explicit 'and' operator in between patterns because 
there is already an implicit 'and' between the sub-patterns of a deconstructing 
pattern.

To finish, there is also an issue with the lack of familiarity, when we have 
designed lambdas, we have take a great care to have a syntax similar to the C#, 
JS, Scala syntax, the concept of guards is well known, to introduce a competing 
feature in term of syntax and semantics, the bar has to be set very high 
because we are forcing people to learn a Java specific syntax, not seen in any 
other mainstream languages*.

For me, the cons far outweigh the pro(s) here, but perhaps i've missed 
something ?

> 
> Draft language specs are under way - I will announce those as soon as they are
> ready.
> 
> Comments welcome as always!

regards,

> 
> Thanks,
> Gavin

Rémi
* Java do not invent things, it merely stole ideas from the other and make them 
its own in a coherent way


Re: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions)

2021-03-03 Thread Remi Forax
- Mail original -
> De: "Brian Goetz" 
> À: "Tagir Valeev" 
> Cc: "amber-spec-experts" 
> Envoyé: Mardi 2 Mars 2021 18:44:27
> Objet: Re: [External] : Pattern assignment statements (extracted from: 
> Primitive type patterns and conversions)

>> A couple of other things to consider:
>> 1. C-style array declaration is not allowed in patterns.
> 
> Wish we could phase out C-style array decls entirely...
> 
>> 2. For local variable declaration, the initializer can omit `new int[]` part:
>> int data[] = {1, 2, 3}
> 
> Good catch.  For some reason I was thinking this was only for fields,
> but it works for locals too.  There's a future story about collection
> literals, but it is unlikely we'll be able to retcon exactly this
> syntax.  (#include "bikeshed-deterrent.h")

the whole story about initializing a local variable with an array is weird,
  int data[] = {1, 2, 3};
compiles but
  int data[];
  data = {1, 2, 3};
does not.

> 
> We may be able to address this by including a conversion from an array
> initializer to the target type of a pattern in an unconditional pattern
> assignment context.

while if think we have specify inference that uses the target type in order to 
avoid to avoid type patterns with a lot of redundant type information,
i'm not sure emulating that particular quirk of Java is a good idea. 

> 
>> 3. Local variable type may affect the initializer type (e.g.
>> List list = List.of()). What about patterns? Can we say that a
>> `List list` pattern is a total pattern over `List.of()`
>> expression type?
> 
> Yes, patterns have a notion of a target type, which is the type they
> have declared as their matchee.  For a type pattern `T t`, that target
> type is T.  We already evaluate whether the pattern is applicable to the
> matchee using this type.

yes, but we have to be careful here, as i said earlier, the inference you want 
when you do an instanceof/cast and the inference we already have can be at odd.
By example, with
  Object o;
  if (o instanceof List<> list) {  }
inferring List has the current inference algorithm does will lead to a 
compile error.

Does it means we have two kind of inference algorithms ? In that case, how it 
works when they interact with each other (when i have a switch inside a lambda 
by example) ?
And in that case, what is the inference algorithm used in pattern assignment 
statements ?

At that point, my head explode :)

Rémi

 


Re: Two new draft pattern matching JEPs

2021-03-03 Thread Remi Forax
- Mail original -
> De: "Gavin Bierman" 
> À: "amber-spec-experts" 
> Envoyé: Jeudi 18 Février 2021 13:33:20
> Objet: Two new draft pattern matching JEPs

> Dear all,
> 
> The next steps in adding pattern matching to Java are coming! I have drafted 
> two
> new JEPs:

I'm slowly trying to wrap my head around these drafts

> 
> - Nested Record and Array Patterns:
> https://bugs.openjdk.java.net/browse/JDK-8260244

For me the section Pattern Matching and record classes is missing a discussion 
about the fact that even if the pattern uses a binding,
the value of that binding is not accessed (so the corresponding record accessor 
is not called) until the binding is used at least once.

In
  static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var 
lr)) {
System.out.println("Top-left Corner: " + x);
}
  }


The bindings 'y', 'c' or 'lr' are not used so the corresponding record 
accessors are not called,
'x' is used so the accessors Rectangle.l(), ColoredPoint.p() and Point.x() are 
called.


I also found the code of the translation of 
"printXCoordOfUpperLeftPointBeforePatterns" confusing,
because the local variable 'c' in this example is not related to the binding 
'c' in the pattern matching example.

  static void printXCoordOfUpperLeftPointBeforePatterns(Rectangle r) {
if (r == null) {
return;
}
ColoredPoint c = r.l();   // here 'c' should be 'l' not 'c'
if (c == null) {
return;
}
Point p = c.p();
if (p == null) {
return;
}
int x = p.x();
System.out.println("Top-left Corner: " + x);
  }

I also think that it will help the reader if record component names in the 
example are not identifiers with only one or two letters like in
  record ColoredPoint(Point p, Color c) {}
  record Rectangle(ColoredPoint ul, ColoredPoint lr) {}

While i think that like lambda, using identifiers with not a lot of letters is 
fine in a pettern, having classical identifiers for record component names will 
help to make the distinction between the component names and the binding names. 

> 
> Comments welcome as always!
> 
> Thanks,
> Gavin

Rémi


Deserialization of a linked list of records

2021-02-26 Thread Remi Forax
[sent to both email list given it's not clear if it's an implementation issue 
or a spec issue]

There is a nice video of how the serialization of records works recently on 
inside.java
https://inside.java/2021/02/23/records-met-serialization/

In the video, Julia explains that the de-serialization works from bottom-up, so 
what if the record instances are a linked list ...
answer: a stack overflow.

Here is a small reproducer.

import static java.util.stream.IntStream.range;

public class RecordSerializationFailure {
  record Link(Object value, Link next) implements Serializable {}

  public static void main(String[] args) throws IOException, 
ClassNotFoundException {
var link = range(0, 1_000_000)
.boxed()
.reduce(null, (next, i) -> new Link(i, next), (_1, _2) -> null);

var path = Path.of("serial.data");
try(var output = Files.newOutputStream(path);
var oos = new ObjectOutputStream(output)) {
  oos.writeObject(link);
}

Link result;
try(var input = Files.newInputStream(path);
var ois = new ObjectInputStream(input)) {
  result = (Link) ois.readObject();
}

System.out.println(link.equals(result));
  }
}

Should this be fixed or not ?

regards,
Rémi


Pattern Matching in Python

2021-02-11 Thread Remi Forax
Hi all,
in the news, Python have been accepted the PEP 642, Pattern Matching in Python 
[1].

The accepted PEP is https://www.python.org/dev/peps/pep-0642/
And the rational is https://www.python.org/dev/peps/pep-0635/

Compared to us,
- bindings are visually like variables not a special keyword (a previous 
proposal was using 'as')
  so "case foo:" capture the value inside and store it into foo which is 
obviously very controversial
  (it's even worst because "foo" doesn't have to be a fresh variable).
  I'm happy that we have not chosen that way :)  

- guards are introduced with "if", which i find more readable than & or &&.
  Using 'if' can be an issue for us because it means that we can not have a 
guard inside the instanceof pattern, but i am not sure this is really a problem.

- constants can be used directly
  I'm still on the fence about that, it's certainly confusing that case 
Point(0,0) means case Point(var x, var y) if x == 0 && y == 0 and not
  equals(new Point(0,0)) but i understand the arguments that people don't care 
because the result is identical and bloating the syntax to make the semantics 
extra clear
  like in C#, case Point(== 0, == 0) doesn't worth the fuss.

- no support for comma separated values because the syntax is too close to the 
tuple syntax,
  we have already chosen to support comma separated values but only at top 
level, not inside a nested pattern.
  They have introduced '|' for OR and it can be used as top level and inside 
nested patterns.
  Note: in Java, using ',' vs '|' in the syntax is not something new, we are 
using '|' for catch and not ','.
  So question, should we introduce a OR pattern ??

- no support for AND and NOT pattern,
  because as i said in an early mail, using tuples is already an implicit AND.

regards,
Rémi

[1] https://lwn.net/Articles/845480/


  1   2   3   4   >