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

Behrang Saeedzadeh behrangsa at gmail.com
Thu Jul 30 07:54:28 UTC 2020


Sweet!

Best regards,
Behrang Saeedzadeh
(Sent from my cellphone.)

On Thu, Jul 30, 2020, 5:17 PM Remi Forax <forax at univ-mlv.fr> wrote:

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