A proposal (pre-proposal?) to add parameter groups to Java
Remi Forax
forax at univ-mlv.fr
Thu Jul 30 07:17:38 UTC 2020
----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Behrang Saeedzadeh" <behrangsa at gmail.com>, "discuss" <discuss at openjdk.java.net>
> Envoyé: Mercredi 29 Juillet 2020 21:18:54
> Objet: Re: A proposal (pre-proposal?) to add parameter groups to Java
> This request is in a category that I like to call "please, can we have
> bad ad-hoc tuples." ("Multiple return" is another one in this
> category.) Which is to say, while it seems like it makes things better,
> it just moves the problem around.
>
> If you declare a method
>
> void line((int x, int y) p1) { ... }
>
> what is the type of P1? What can I do with it, besides extract x and
> y? Can I return one? Can I declare a local variable of that type?
> (Your proposal suggests no to all of these, as you only mentioned method
> parameter grouping.)
>
> Which is to say, you are creating a new kind of structured literal `(a,
> b)`, but which _only_ can be used as a parameter to a method that wants
> one of these. You can see how this would be limited. The trouble with
> such "easy" proposals is that they help you move forward one step, and
> then are stuck in the exact same way as before, just one step farther.
>
> What you really want is structural tuples, where for any types T and U,
> `(T, U)` is a type which is an ordered pair of a `T` and `U`, and you'd
> be able to use this as a return type, parameter type, local variable
> type, array component type, type bound for generics, etc. But, that's a
> much bigger thing than what you are asking. Relatively few languages
> have succeed at mixing nominal and structural types very effectively
> (Java's nod to structural types is arrays, and there are visible seams
> where array types meet the rest of the type system.)
>
> Many languages (e.g, Haskell, ML) do have structural tuples. Such
> languages tend to integrate pattern matching into the language, so that
> destructuring is built into how we declare functions. For example, in
> Haskell, I can define:
>
> type IntInt = (Int, Int)
>
> which says IntInt is a tuple of Int and Int, and then declare a function
> like your line:
>
> line :: IntInt -> IntInt -> ()
>
> (which means "line is a function that takes two IntInt and returns
> void") and then I can declare a definition for line:
>
> line p q = ...
>
> where each of p and q are of type IntInt. Alternately, I can define
> line with a destructuring pattern match:
>
> line (x1, y1) (x2, y2) = ...
>
> These two declarations are equivalent, since the type of `line` is that
> it takes two (int, int) pairs; the first binds p and q to pairs, and the
> latter matches structurally on the pairs and binds x1, y1, x2, y2 to the
> coordinates. (I could do the same without having declared IntInt; it's
> just the equivalent of `typedef` in C.)
>
> So yes, there is precedent for such a thing -- but in languages that are
> pretty different from Java.
>
> What we do in the Java world, instead, is lean on _nominality_. Rather
> than structural types launching into being by simply uttering their
> structure, we declare them. Java's nominal function types are
> functional interfaces; Java's nominal tuples are records.
>
> So you could declare your method as:
>
> record Point(int x, int y) { }
>
> void line(Point p1, Point p2) { ... }
>
> Within line(), you can (eventually) use pattern matching to destructure
> the point:
>
> void line(Point p1, Point p2) {
> Point(var x1, var y1) = p1; // destructure p1 with Point
> deconstructor into x1, y1
> Point(var x2, var y2) = p2;
> }
>
> (when we have destructuring patterns, which will come to records
> eventually.) You might even be able to put the destructuring in the
> declaration (no promises!):
>
> void line(Point(var x1, var y1) p1,
> Point(var x2, var y2) p2) { }
>
> and then all of {x,y,p}{1,2} would be bound in the body. This is a more
> general solution, since Point is an ordinary class type, so can be used
> anywhere.
which can be simplified to (no need to declare p1 and p2)
void line(Point(var x1, var y1), Point(var x2, var y2)) { }
one interesting thing is that like lambdas with functional interfaces, a record is a nominal type that can be used by inference as a structural type, so at call site, instead of writing
line(new Point(1, 2), new Point(3, 4))
one can write
line((1, 2), (3, 4))
and with a pinch of valhalla, if the record is declared inline
inline record Point(int x, int y) { }
the compiler see the method line declared as line(Point, Point) but the VM see it as line(int, int, int, int) so you get the abstraction of using Point without the cost of allocating the two Point on the heap.
Rémi
>
>
>
> On 7/29/2020 3:37 AM, Behrang Saeedzadeh wrote:
>> When we apply the Introduce Parameter Object [1
>> ] refactoring on methods that take many arguments we usually have to
>> define a wrapper class for one or more of the arguments.
>>
>> For example, we can refactor:
>>
>> void line(int x1, int y1, int x2, int y2) {
>>
>> }
>>
>> line(0, 0, 10, 10);
>>
>>
>> into:
>>
>> class Point {
>> private final int x;
>> private final int y;
>>
>> public Point(int x, int y) {
>> this.x = x;
>> this.y = y;
>> }
>>
>> // getters
>> }
>>
>> void line(Point p1, Point p2) {
>>
>> }
>>
>> line(new Point(0, 0), new Point(10, 10));
>>
>> Wouldn't it be nicer if we could eliminate the need to define Point
>> and creating p1 and p2 to wrap x1, y1, x2, and y2
>> and instead grouped parameters in the method declaration in a way similar
>> to this:
>>
>> void line((int x, int y) p1, (int x, int y) p2) {
>> System.out.println(p1.x + " " + p1.y);
>> System.out.println(p2.x + " " + p2.y);
>> }
>>
>> line((0, 0), (10, 10));
>>
>> What do you think?
>>
>> P.S: Are there any existing languages with a similar feature?
>>
>> [1] https://refactoring.guru/introduce-parameter-object
More information about the discuss
mailing list