Method Chaining (enhancing Java syntax)

Alex Buckley alex.buckley at oracle.com
Tue Jun 13 17:57:30 UTC 2023


// Changed recipient from jdk-dev to amber-dev

A couple of thoughts:

1. A `this` method typically makes progress by mutating the receiver, 
yet the arc of the Java language bends toward less mutability, not more 
mutability.

2. The scope of the feature is sufficiently limited, compared to self 
types, that it can sidestep the soundness problems raised by Maurizio. 
Limited scope helps the feature but also hurts it. The positive: by 
polishing covariant return to work better for deep class hierarchies, 
you improve abstraction for clients of the deep class hierarchy -- 
somewhat akin to sealed classes. The negative: polishing covariant 
return is pretty niche, yet everyone would have to learn about it (in 
case they find it in someone's code). Project Coin didn't move forward 
with method changing in Java 7 for basically the same "helpful, but not 
enough" reason.

Alex

On 6/13/2023 10:17 AM, Tomáš Bleša wrote:
> Hi,
> 
> thank you for comments and references. I didn’t mean we should create 
> special self-type. The proposed syntax:
> 
>    public *this* hello() {}
> 
> doesn’t mean “/returns an object of the same type as my class/”.
> 
> but rather
> 
> “/returns the same instance it is called on/”. The fact that it is also 
> the same type is a useful byproduct. Please note that I used lowercase T 
> to emphasize it is the instance not type.
> 
> Under the hood nothing has to be returned (on the call stack) and the 
> method should compile to:
> 
>    public *void* hello() {}
> 
> That’s why using this (ThisClass/MyClass) in method arguments or other 
> places doesn’t make sense to me. (Passing the same instance to method 
> which already has this reference.) It also doesn’t make sense (or 
> little) to use it on static methods like:
> 
>    public *static this* hello();
> 
> *Ad Generics) *As you pointed out it doesn’t work well with deep class 
> hierarchy and also reduces readability (adds boilerplate). Imagine for 
> example big library representing UI (like DOM structure):
> 
> UIElement// method css(…) here
>      UILayout
>          UIGridLayout
>              …
>      UIFormElement
>          UISelect
>              UIMultiSelect…// has method maxSelection()
> 
> // compilation error
> var mySelect = new UIMultiSelect().css(„color”, „green”)
> .addClass(„attention”).maxSelection(3);
> 
> This is basically my use-case. I have 60..100 subclasses.
> 
> *Ad "Is the feature important enough?")*
> 
> Technically all language features are just “a syntactic sugar” to the 
> assembler (bytecode). I lived many years without switch-expressions, 
> try-with-resource,… :-)
> 
> All the best,
> Tomas
> 
>> 13. 6. 2023 v 12:51, Maurizio Cimadamore 
>> <maurizio.cimadamore at oracle.com <mailto:maurizio.cimadamore at oracle.com>>:
>>
>> Hi,
>> the topic you mention is sometimes known, in type-system literature, 
>> as “self types”. The topic of adding self types to Java was 
>> reasearched few years ago (see [1, 2]). While adding self-types 
>> increases the expressiveness of the language (e.g. allowing support 
>> for so called “binary methods” - e.g. typing methods such as 
>> Object::equals correctly), such extension makes the language unsound. 
>> Given this declaration (which I borrow from the cited paper):
>>
>> |class Node { ... public void setNext(ThisClass next) { ... } } |
>>
>> And given the following generic method declaration:
>>
>> |static void setNextStatic(Node node1, Node node2) { 
>> node1.setNext(node2); // whooops } |
>>
>> Here we have an issue, because it is now possible to pass two 
>> instances of Node with different “self types”, and this program would 
>> still type-check. In other words, subtyping here works against us - 
>> because I can declare some subclass of Node, e.g. MyColoredNode <: 
>> Node, and then call |setNextStatic(new Node(), new MyColoredNode())|, 
>> which clearly violates the specification of Node::setNext, but the 
>> type system is not powerful enough to detect that.
>>
>> Typically this tension is resolved by adding support for so called 
>> “exact types” (denoted as @T). A variable with an exact type 
>> guarantees that it can only ever hold values that are the same as its 
>> static type. So, e.g. assigning a MyColoredNode object to a variable 
>> of type @Node would fail. Equipped with this new weapon, we can go 
>> back and change the declaration of setNext to:
>>
>> |public void setNext(@ThisClass next) { ... } |
>>
>> Which makes things sound again.
>>
>> This is a common tale when coming up with new language ideas - e.g. we 
>> start from a relatively constrained problems (e.g. how to better 
>> express covariant returns, in a way that doesn’t force us to 
>> re-override a method in all subclasses?) and we find something that 
>> works. Then we see what happens if we use the same construct in places 
>> we did not anticipate (in this case, usage of |this| type in parameter 
>> position) and see what happens. Almost every non-trivial type-system 
>> extension I’ve been working with in the past 15 years starts showing 
>> some kind of subtle interactions when looked at it that way. And 
>> “this” is no exception.
>>
>> At which point the question becomes: is the feature important enough 
>> to either (a) swallow the cost of any other dependent feature it 
>> brings about (e.g. exact @ types), or (b) restrict the usage of the 
>> feature only in the places where it is well behaved (e.g. return 
>> position) ? The former has a complexity cost (introduction of a new 
>> kind of type), whereas the latter has an irregularity cost (e.g. the 
>> new types can only be used in few selected places). My (subjective) 
>> judgment is that, in this case the feature you propose is not worth 
>> paying any of these costs, given that there are some alternatives to 
>> achieve the same thing.
>>
>> On that subject, one alternative you have left out in your description 
>> is to use the generic type system to implement a poor man “self type” 
>> - e.g.
>>
>> |class Foo<T extends Foo<T>> { T get(); } class SubFoo extends 
>> Foo<SubFoo> { } |
>>
>> This is not perfect (especially when working with complex class 
>> hierarchies) but I have seen this technique used with success many 
>> times (e.g. in the implementation of the Stream API itself).
>>
>> Cheers
>> Maurizio
>>
>> [1] - https://link.springer.com/chapter/10.1007/978-3-540-24851-4_18
>> [2] - https://www.sciencedirect.com/science/article/pii/S0167642313000038
>>
>> On 09/06/2023 17:35, Tomáš Bleša wrote:
>>
>>
>>
>>> //* I sent the following to discuss@ mailing list yesterday. (wrong 
>>> list for the topic) I hope this will be more appropriate mailing list *//
>>>
>>> Hi all,
>>>
>>> this is a request for feedback on a topic that has been on my mind 
>>> for a few weeks. I have written a short document in JEP format and 
>>> would like to ask you to comment if you find the described proposal 
>>> useful.
>>>
>>> Thanks,
>>> Tomas Blesa
>>> __________________________________
>>>
>>> Summary
>>> -------
>>> Enhance the Java language syntax to better support the method 
>>> chaining (named parameter idiom) programming pattern.
>>>
>>> Goals
>>> -----
>>> The primary goal is to remove unnecessary boilerplate code in class 
>>> methods designed for type-safe chained calls, especially when 
>>> combined with inheritance.
>>>
>>> Motivation
>>> ----------
>>> [Method chaining](https://en.wikipedia.org/wiki/Method_chaining) is a 
>>> widely used and popular programming pattern, particularly in creating 
>>> libraries (APIs) or configuration objects. Programmers can easily 
>>> create a method that returns `this` with a method signature that 
>>> specifies the returning type of the containing class.
>>>
>>> ```java
>>> class Shape {
>>>     public Shape scale(double ratio) {
>>>         // recalculate all points
>>> return this;
>>>     }
>>> }
>>> ```
>>>
>>> The problem arises when we combine this pattern with inheritance. We 
>>> can lose type information when calling the method on a subclass. For 
>>> example, let's create two subclasses of the `Shape` superclass:
>>>
>>> ```java
>>> class Rectangle extends Shape {
>>>     public Rectangle roundCorners(double pixels) {
>>>         // ...
>>> return this;
>>>     }
>>> }
>>>
>>> class Circle extends Shape {
>>> }
>>> ```
>>>
>>> Now, imagine the following piece of code using the mini-library above:
>>>
>>> ```java
>>> var myRect = new Rectangle().scale(1.2).roundCorners(10);
>>> ```
>>>
>>> The code won't compile because `scale()` returns the type `Shape`, 
>>> which doesn't have the `roundCorners` method. There is also a problem 
>>> even without the final `roundCorners()` call:
>>>
>>> ```java
>>> var myRect = new Rectangle().scale(1.2);
>>> ```
>>>
>>> The inferred type of `myRect` is `Shape` and not `Rectangle`, so the 
>>> following line will also be invalid:
>>>
>>> ```java
>>> myRect.roundCorners(10);
>>> ```
>>>
>>> Straightforward solutions to the problem could be:
>>>
>>> 1) Override the `scale()` method in all subclasses and change the 
>>> return type:
>>>
>>> ```java
>>> class Rectangle extends Shape {
>>>     // ...
>>> @Override
>>>     public Rectangle scale(double ratio) {
>>> super.scale(ratio);
>>> return this;
>>>     }
>>> }
>>> ```
>>>
>>> 2) Split object construction and method calls:
>>>
>>> ```java
>>> var myRect = new Rectangle();
>>> myRect.scale(1.2);
>>> myRect.roundCorners(10);
>>> ```
>>>
>>> 3) Partial solution - reorder chained calls (if possible):
>>>
>>> ```java
>>> var myRect = new Rectangle();
>>> myRect.roundCorners(10).scale(1.2); // roundCorners called first
>>> ```
>>>
>>> All of these solutions add unnecessary lines of code, and as the 
>>> library of shapes grows, keeping the desired return type will 
>>> introduce more and more boilerplate code.
>>>
>>> Description
>>> -----------
>>> The proposed solution to the problem described in the previous 
>>> section is to extend the Java syntax for the returned type in method 
>>> signatures:
>>>
>>> ```java
>>> class Shape {
>>>     public this scale(double ratio) { // <=== returns this
>>>         // recalculate all points
>>> return this;
>>>     }
>>> }
>>> ```
>>>
>>> Methods declared or defined as returning `this` can only return the 
>>> instance on which they are called. The following code will be 
>>> type-safe and perfectly valid:
>>>
>>> ```java
>>> var myRect =                     // inferred Rectangle type
>>>     new Rectangle()              // returns Rectangle instance
>>> .scale(1.2)                  // returns Rectangle instance
>>> .roundCorners(10);           // returns Rectangle instance
>>> ```
>>>
>>> The constructed type `Rectangle` is preserved throughout the entire 
>>> call chain.
>>>
>>> It is possible to override methods returning `this`, but the 
>>> subclass' implementation must also be declared with the `this` 
>>> keyword instead of a concrete returning type.
>>>
>>> It is even possible to remove the explicit return statement altogether:
>>>
>>> ```java
>>> class Shape {
>>>     public this scale(double ratio) {
>>>         // recalculate all points
>>>     }
>>> }
>>> ```
>>>
>>> Or simply remove the value `this` from the return statement:
>>>
>>> ```java
>>> class Shape {
>>>     public this scale(double ratio) {
>>>         // recalculate all points
>>>         if (condition) return;         // <== automatically returns this
>>>         // do something else
>>>     }
>>> }
>>> ```
>>>
>>> In fact, methods returning `this` can be compiled to the same 
>>> bytecode as methods returning `void`. This is because the instance 
>>> reference (and the returned value) is already known to the caller, 
>>> eliminating the need to pass that value back through the call stack. 
>>> As a result, both CPU cycles and memory are saved.
>>>
>>> In the Java world, it is common to create getters and setters 
>>> according to the Java Beans specification in the form of 
>>> `getProperty`/`setProperty` pairs or `isProperty`/`setProperty`. 
>>> Setters are defined as returning `void`. These setters can be more 
>>> useful if defined as returning `this`:
>>>
>>> ```java
>>> class Customer {
>>>     public this setFirstname() { ... }
>>>     public this setSurname() { ... }
>>>     public this setEmail() { ... }
>>> }
>>> ```
>>>
>>> This allows for more concise usage when constructing and configuring 
>>> an instance without adding more code:
>>>
>>> ```java
>>> customers.add(
>>>     new Customer()
>>> .setFirstname(resultSet.getString(1))
>>> .setSurname(resultSet.getString(2))
>>> .setEmail(resultSet.getString(3))
>>> );
>>> ```
>>>
>>> It is also possible to declare an interface with methods returning 
>>> `this`:
>>>
>>> ```java
>>> interface Shape {
>>>     this scale(double ratio);
>>> }
>>> ```
>>>
>>> In this case, all implementing classes must define the method as 
>>> returning `this`.
>>>
>>> The proposed syntax is a bit less useful for enums or records, as 
>>> neither of them allows for inheritance. But enums and records can 
>>> also implement interfaces and for this reason and for overall 
>>> consistency, "return this" syntax should be allowed for enums and 
>>> records.
>>>
>>> To accommodate the syntax with the Java Reflection API, it will 
>>> probably be required to create a special final placeholder class 
>>> `This` (with an uppercase "T"), similar to `java.lang.Void`.
>>>
>>> Alternatives
>>> ------------
>>> It is probably possible to help auto-generate overriding methods in 
>>> subclasses using annotation processing, but this option wasn't fully 
>>> explored. However, such an approach would add extra unnecessary code 
>>> to compiled subclasses and go against the primary goal of reducing 
>>> boilerplate.
>>>
>>> Risks and Assumptions
>>> ---------------------
>>> The proposed syntax is likely to break the compatibility of 
>>> library-dependent code whose author decides to switch to the "return 
>>> this" syntax between versions.
>>>
>>> Older code that looks like this:
>>>
>>> ```java
>>> class MyUglyShape extends Shape {
>>> @Override
>>>     public MyUglyShape scale(double ratio) {
>>> return this;
>>>     }
>>> }
>>> ```
>>>
>>> will have to be rewritten as:
>>>
>>> ```java
>>> class MyUglyShape extends Shape {
>>> @Override
>>>     public this scale(double ratio) {    // signature change
>>>         // optional removal of the return this statement
>>>     }
>>> }
>>> ```
>>>
>>> or
>>>
>>> ```java
>>> class MyUglyShape extends Shape {
>>> // override removed
>>> }
>>> ```
>>>
>>> This problem can be mitigated with the help of smart IDEs 
>>> automatically suggesting such refactoring.
>>>
>>> Another possible risk is breaking old code that relies on the 
>>> Reflection API for scanning the returning types of methods.
>>
>>
>>> 


More information about the amber-dev mailing list