Proposal: generic receiver parameter

Brian Goetz brian.goetz at oracle.com
Mon Apr 4 22:06:54 UTC 2022


This is a familiar request, which often goes under the name of "self 
types".  It shows up most commonly in hierarchies of builders, but can 
also show up in other "fluent" APIs.  (This feature is somewhat related 
to another commonly requested feature (also about chaining), which is to 
allow "void" methods to be chained as if they "return this".)

The addition of explicit receivers in 8 (which was introduced at the 
time to allow receiver annotations, but we always knew that it might 
find use in type refinement as well) offers another option for how to 
express this. Effectively, the only thing such a method can return under 
these constraints is `this` (well, also null), since it has no other 
way(s) to produce a witness to T.

This observation:

> However, if the fluent method has multiple type parameters, then its 
> invocations that explicitly supply type arguments should be allowed to 
> omit the receiver type argument; so the receiver type parameter must 
> be declared either first or last. 

is what pushes this particular approach to exposing it over the edge; 
this is the sort of feature interaction where a small feature has a 
non-orthogonal effect on a bigger feature (making some sort of type 
parameters more "special" than others), which we try very hard to 
avoid.  There's way too high a chance that were we to do this now (which 
feels like a "small irregularity" when you're focused on what you want 
to accomplish now), that my successor would curse me for it for what it 
forecloses on.


Overall, the notion of "self types" or "this return" is a "mostly 
harmless" feature, whose intrusion is relatively limited, but not one 
that we've historically placed a very high priority on.  They both 
mainly exist to support chaining, and chaining is simply not that 
incrementally expressive to justify adding extra language features to 
squeeze a little extra value out of it.





On 4/3/2022 12:31 PM, Mohamed Maaliki wrote:
> Hello. I've designed multiple fluent APIs with inheritance. While on 
> one hand their usage can be concise and readable, on the other their 
> implementation is verbose.
>
> Let's say that we have
> ```
> class A {
>     A withX(int x) {...}
> }
> class B extends A {
>     B withY(int y) {...}
> }
> ```
> . `new B().withX(x).withY(y)` is erroneous because `A::withX` returns 
> `A`. I see 2 solutions to this problem:
> 1. overriding `withX` with return type `B`; and
> 2. adding a "`this` type" parameter `T extends A<T>` to `A`.
> Solution #1 is acceptable in small classes (which are unlikely) that 
> don't change but if `A` receives new methods or is large, then the 
> implementation of `B` becomes very noisy.
> Solution #2 reduces noise in `B`'s implementation but it makes usage 
> of `A` and its subclasses verbose instead: instantiation needs a 
> diamond (`new B<>().withX(x).withY(y)`); field and parameter 
> declarations of type `B` have to redundantly specify a wildcard type 
> parameter (`B<?> parameter`); and such fluent methods must perform an 
> unsafe cast to `T` when returning.
> This API that was initially intended to be readable now hurts 
> readability.
>
> Since the JLS allows the receiver to be declared explicitly, the 
> problem could be solved more elegantly by extending it to allow the 
> receiver to be generic:
> ```
> class A {
>     <T extends A> T withX(T this, int x) {...}
> }
> class B extends A {
>     <T extends B> T withY(T this, int y) {...}
> }
> ```
> . The compiler substitutes the type of the receiver for `T`. Now `B` 
> can extend `A` without a redundant and potentially unsafe type 
> parameter or having to override `withX` and their usages are clean:
> ```
> void useB(B b) {
>     doSomething(b.withX(x).withY(y));
> }
> ```
> . We can see that `T` is always assignable to its enclosing type; so 
> we could eliminate the explicit upper bound: `<T> T withX(T this, int 
> x) {...}`.
>
> However, if the fluent method has multiple type parameters, then its 
> invocations that explicitly supply type arguments should be allowed to 
> omit the receiver type argument; so the receiver type parameter must 
> be declared either first or last.
>
> Regards.
>


More information about the amber-dev mailing list