How to specify "own type" as return type in Kotlin
Asked Answered
Z

7

42

Is there a way to specify the return type of a function to be the type of the called object?

e.g.

trait Foo {
    fun bar(): <??> /* what to put here? */ {
        return this
    }
}

class FooClassA : Foo {
    fun a() {}
}

class FooClassB : Foo {
    fun b() {}
}

// this is the desired effect:
val a = FooClassA().bar() // should be of type FooClassA
a.a()                     // so this would work

val b = FooClassB().bar() // should be of type FooClassB
b.b()                     // so this would work

In effect, this would be roughly equivalent to instancetype in Objective-C or Self in Swift.

Zebrass answered 10/4, 2015 at 15:17 Comment(1)
I am trying to figure out the same, but for both base calss and a subclass: hereCumquat
S
45

There's no language feature supporting this, but you can always use recursive generics (which is the pattern many libraries use):

// Define a recursive generic parameter Me
trait Foo<Me: Foo<Me>> {
    fun bar(): Me {
        // Here we have to cast, because the compiler does not know that Me is the same as this class
        return this as Me
    }
}

// In subclasses, pass itself to the superclass as an argument:
class FooClassA : Foo<FooClassA> {
    fun a() {}
}

class FooClassB : Foo<FooClassB> {
    fun b() {}
}
Sirup answered 10/4, 2015 at 16:18 Comment(5)
Trait was deprecated in Kotlin M12, we shall use the keyword interface instead: blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-outBushire
Is there any way for the compiler to infer the correct behavior? Or, if it is absolutely necessary to cast, suppress the warning from the Kotlin IDE plugin: Unchecked cast: Foo<Me> to Me?Berman
Bravo! Smart answer. I wonder if in 2021 there is a better one? Anyway, great answerGallinule
it would be nice if Kotlin supported referencing the implementing type using something like Self a la Rust, but I suppose thats a pipe dream considering this is a JVM language we're talking about... @OzShabat, there really isn't any other way than this generic soup. Frankly, such code is no different from doing wacky stuff like implementing object orientation via vtables directly in C.Gilud
@Berman nope, either kotlin isn't smart enough, or there are JVM considerations which prevent it from trying. if its the first case, things can get better. If its the latter, lament the fact that java continues to shoot itself in its foot since its inception.Gilud
M
29

You can return something's own type with extension functions.

interface ExampleInterface
// Everything that implements ExampleInterface will have this method.
fun <T : ExampleInterface> T.doSomething(): T {
    return this
}

class ClassA : ExampleInterface {
    fun classASpecificMethod() {}
}

class ClassB : ExampleInterface {
    fun classBSpecificMethod() {}
}

fun example() {
    // doSomething() returns ClassA!
    ClassA().doSomething().classASpecificMethod()
    // doSomething() returns ClassB!
    ClassB().doSomething().classBSpecificMethod()
}
Mccord answered 2/7, 2017 at 20:37 Comment(0)
L
8

You can use an extension method to achieve the "returns same type" effect. Here's a quick example that shows a base type with multiple type parameters and an extension method that takes a function which operates on an instance of said type:

public abstract class BuilderBase<A, B> {}

public fun <B : BuilderBase<*, *>> B.doIt(): B {
  // Do something
  return this
}

public class MyBuilder : BuilderBase<Int,String>() {}

public fun demo() {
  val b : MyBuilder = MyBuilder().doIt()
}

Since extension methods are resolved statically (at least as of M12), you may need to have the extension delegate the actual implementation to its this should you need type-specific behaviors.

Livorno answered 16/9, 2015 at 16:47 Comment(0)
K
8

Recursive Type Bound

The pattern you have shown in the question is known as recursive type bound in the JVM world. A recursive type is one that includes a function that uses that type itself as a type for its parameter or its return value. In your example, you are using the same type for the return value by saying return this.


Example

Let's understand this with a simple and real example. We'll replace trait from your example with interface because trait is now deprecated in Kotlin. In this example, the interface VitaminSource returns different implementations of the sources of different vitamins.

In the following interface, you can see that its type parameter has itself as an upper bound. This is why it's known as recursive type bound:

VitaminSource.kt

interface VitaminSource<T: VitaminSource<T>> {
    fun getSource(): T {
        @Suppress("UNCHECKED_CAST")
        return this as T
    }
}

We suppress the UNCHECKED_CAST warning because the compiler can't possibly know whether we passed the same class name as a type argument.

Then we extend the interface with concrete implementations:

Carrot.kt

class Carrot : VitaminSource<Carrot> {
    fun getVitaminA() = println("Vitamin A")
}

Banana.kt

class Banana : VitaminSource<Banana> {
    fun getVitaminB() = println("Vitamin B")
}

While extending the classes, you must make sure to pass the same class to the interface otherwise you'll get ClassCastException at runtime:

class Banana : VitaminSource<Banana>    // OK
class Banana : VitaminSource<Carrot>    // No compiler error but exception at runtime

Test.kt

fun main() {
    val carrot = Carrot().getSource()
    carrot.getVitaminA()

    val banana = Banana().getSource()
    banana.getVitaminB()
}

That's it! Hope that helps.

Knox answered 2/1, 2021 at 16:39 Comment(2)
Is there a way to get a compile-time error?Timmy
@FelixDombek, unfortunately none that I know of. That's the only drawback of this pattern. We just need to carefully specify the same class. Maybe write a note in comments for future developers.Knox
S
0

Depending on the exact use case, scope functions can be a good alternative. For the builder pattern apply seems to be most useful because the context object is this and the result of the scope function is this as well.

Consider this example for a builder of List with a specialized builder subclass:

open class ListBuilder<E> {
    // Return type does not matter, could also use Unit and not return anything
    // But might be good to avoid that to not force users to use scope functions
    fun add(element: E): ListBuilder<E> {
        ...
        
        return this
    }

    fun buildList(): List<E> {
        ...
    }
}

class EnhancedListBuilder<E>: ListBuilder<E>() {
    fun addTwice(element: E): EnhancedListBuilder<E> {
        addNTimes(element, 2)
        return this
    }

    fun addNTimes(element: E, times: Int): EnhancedListBuilder<E> {
        repeat(times) {
            add(element)
        }
        return this
    }
}

// Usage of builder:
val list = EnhancedListBuilder<String>().apply { 
    add("a") // Note: This would return only ListBuilder
    addTwice("b")
    addNTimes("c", 3)
}.buildList()

However, this only works if all methods have this as result. If one of the methods actually creates a new instance, then that instance would be discarded.


This is based on this answer to a similar question.

Snapback answered 22/7, 2021 at 0:40 Comment(0)
A
-2
class foo(){
fun boo():foo{
return this
  }
}
Anacoluthia answered 14/10, 2023 at 22:53 Comment(0)
D
-3

You can do it also via extension functions.

class Foo
fun <T: Foo>T.someFun(): T {
    return this
}
Foo().someFun().someFun()
Defendant answered 3/5, 2021 at 4:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.