A peek at the roadmap for pattern matching and more

forax at univ-mlv.fr forax at univ-mlv.fr
Thu Aug 13 13:06:10 UTC 2020


> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Jeudi 13 Août 2020 03:51:51
> Objet: Re: A peek at the roadmap for pattern matching and more
[...] 

>> I know that you have consider something like this, but i prefer making the
>> deconstructor a method returning a tuple at Java level, to be closer to the JVM
>> level.

> Yes, we did consider this, but I don’t like it, because it’s fake. Having
> tuple-like syntax that you could only use in one place would feel like “glass
> 99% empty.” Unless people can use tuples as returns, destructure them, store
> them in variables, denote their types, pass them to methods, etc, it will just
> be a tease. No one will thank us, and I don’t think it really carries the
> message home the way the current framing does.

ok, let's take a step back because i think the current proposal has fall in a kind of local maximum in term of design. 

We want a destructor, a destructor is a method that returns a list of values the same way a constructor takes a list of parameters. 
The way to represent that list of value is to use a record. 

So we can already write a de-constructor with the current version of Java, 
With a mutable Point 
class MutablePoint { 
int x, y; 
record PointTuple(int x, int y) { } 

public PointTuple deconstructor() { 
return new PointTuple(this.x, this.y); 
} 
} 

We need to enhance a little the compiler because we want to have different deconstructor but while the classfile let us to have several methods with the same name and same parameters but different return type, Java doesn't allow that. 
So the name deconstructor is considered as special by the compiler, so one can write 
class MutablePoint { 
int x, y; 
record PointTuple(int x, int y) { } 
record Point3DTuple(int x, int y, int z) { } 

public PointTuple deconstructor() { 
return new PointTuple(this.x, this.y); 
} 
public Point3DTuple deconstructor() { 
return new Point3DTuple(this.x, this.y, 0); 
} 
} 

And that enough, we don't need more to declare a constructor. 
Here we are half way to the current proposal, because we are forcing users to explicitly declare the record, but we have face the same kind of choice with lambdas, should we let the compiler transform (int, int -> int) into a synthetic functional interface and decide to not follow on that idea. Here for a reason, i will be happy to heard, you have decided to cross the rubicon. 

Let say i'm ok with that, deconstructors are not the only place we may want a method to return several values, so i see no point to only enable that feature for deconstructors, 
i should wuth by example a method minMax that returns both the minimum and the maximum of an array 
public static (int min, int max) minMax(int[] array) { 
... 
} 
the compiler will generate a synthetic record, so the generated code is equivalent to 
public record FunnyNameWithIntMinAndIntMaxInIt(int min, int max) { } 
public static (int min, int max) minMax(int[] array) { 
... 
return new FunnyNameWithIntMinAndIntMaxInIt(min, max); 
} 

On interesting question is inside minMax, how write the return given that the record is declared with a synthetic name unknown from the user, 
for the simple answer is to use the syntax (min, max) 
So our example can be written to 
public static (int min, int max) minMax(int[] array) { 
... 
return (min, max); 
} 

At that point, if we take a look the compiler does two different operations, first it desugar the return type (int min, int max) to the record FunnyNameWithIntMinAndIntMaxInIt, 
then it uses inference to convert the syntax (min, max) to new FunnyNameWithIntMinAndIntMaxInIt(min, max). 

I see no reason to not allow this inference to work with user defined record, so the class MutablePoint can be written that way 
class MutablePoint { 
int x, y; 
record PointTuple(int x, int y) { } 
record Point3DTuple(int x, int y, int z) { } 

public PointTuple deconstructor() { 
return (this.x, this.y); // inferred as PointTuple 
} 
public Point3DTuple deconstructor() { 
return (this.x, this.y, 0); // inferred as Point3DTuple 
} 
} 

Now, let's take a look to the use-site, where a desconstructor is used, 
it can be in a switch, 
MutablePoint p = ... 
switch(p) { 
case MutablePoint(var x, var y): ... 
} 

or it can be when doing a de-structured assignment 
MutablePoint p = ... 
MutablePoint(var x, var y) = p; 

in both case, we can use inference in the same way we can infer the constructor, we can infer part of the de-structuring pattern, 
so it's reasonable given that a constructor and a de-constructor are dual. 
So you can wirte: 
MutablePoint p = ... 
switch(p) { 
case (var x, var y): ... // inferered MutablePoint 
} 
or 
MutablePoint p = ... 
(var x, var y) = p; 

So the good news is that we can have desconstructors with only a small tweak of the compiler but it will argue that if we introduce the notion of synthetic record, we should also implement inference for record constructors and the de-structuring patterns. 

regards, 
Rémi 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200813/50ba42dc/attachment-0001.htm>


More information about the amber-spec-experts mailing list