A proposal (pre-proposal?) to add parameter groups to Java

Brian Goetz brian.goetz at oracle.com
Wed Jul 29 19:18:54 UTC 2020


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.



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