Although the DSL features in Kotlin mostly rendered builder pattern needless, there are some cases when self returning functions have their cause. A self returning function is a function returning the scoped object itself and used for fluent API-s. An often used pattern:
class X {
private var y : T
fun withY(value: T) : X {
y = value
return this
}
}
Then one can use these functions:
val x = X().withY(v).withZ().withA(a)
I know, that a very similar effect could be achieved by apply
or also
, but there are situations when that is not as fluent as the above form, and shortly I will show other limitations of them.
Returning to the original idea, the traditional syntax have boilercode (return this
) and it is not clear in the API syntax whether the function returns the same object or a copy (like in immutable classes). With this syntax, it may be simplified and made clean. A possible example for the syntax could be:
fun withY(value: T) : this { y = value }
At first, it looks like it gives little gain over
fun withY(value: T) = this.apply { y = value }
but it shorten the code and makes it explicit that the returned object is the same. Also it could render polymorphically: an inherited class would safely return its static class even when the function isn’t overridden in it.
Let’s see the following example:
open class P {
fun a() = this.apply { println("Setting a") }
}
class C : P() {
fun b() = this.apply { println("Setting b") }
}
fun main() {
C().a().b() // Won't compile
}
This is a pitfall of builder pattern over inheritance. Solving this usually leads to a quite ugly code involving self-refered generics, necessary, but meaningless abstract classes and lots of suppressed cast warnings:
abstract class A<R : A<R>> {
@Suppress("UNCHECKED_CAST")
fun a() = this.apply { println("Setting a") } as R
}
// An "empty" class for hiding the implementation-only generic
class P : A<P>()
// C logically inherits from P, but it is not visible any more
class C : A<C>() {
fun b() = this.apply { println("Setting b") }
}
fun main() {
P().a() // Compile
P().a().b() // Won't compile
C().a().b() // Compile
}
With the suggested solution, the code would clean up to:
open class P {
fun a() : this { println("Setting a") }
}
class C : P() {
fun b() : this { println("Setting b") }
}
fun main() {
C().a().b() // Compile
}
As a conclusion, my suggestion would give the following benefits:
- Slightly shorter and cleaner code by itself
- Explicit API declaring the returned object is not a copy, but the object itself
- Could be “auto polymorphic”: the compiler knows the exact type of the function call, it could safely return that one (the above cast is meant to be safe and implicit and the generic parameter is not needed).
I am looking forward your opinion!
Balage