> De: "Brian Goetz" <[email protected]>
> À: "Remi Forax" <[email protected]>
> Cc: "amber-spec-experts" <[email protected]>
> 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