Proposal: generic receiver parameter
Mohamed Maaliki
username11681 at gmail.com
Sun Apr 3 16:31:03 UTC 2022
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