Default Types for Generics

I could not find an existing topic regarding this issue but maybe my keywords were simply not the right ones to search for. Regardless, here it comes.

I would love to see default types for generics in Kotlin like Rust has it:

The most prominent example that could make use of this would be Comparable<T> by changing it to Comparable<T = this> but there are more interesting use cases.

sealead class S
class A : S()
class B : S()

open class X<T : A>
class Y : X<B>()

Constructing X is not possible without specifying the concrete type right now, hence, one has to write X<A>() everywhere. This is cumbersome and repetitive and something that was clear for X from the very beginning.

open class X<T : A = A>
class Y() : A<B>()

Now it is possible to construct X without specifying A everywhere: X()

4 Likes
3 Likes

I didn’t expect that that topic would contain the same problem judging from the title.

Sorry to bug you but are there any updates on this? This would be amazing. Also, is there an official youtrack issue I can follow? Thanks.

Based on this post here I guess that this is something that will be part of 1.4 or will be implemented in one of the 1.4.x releases. I haven’t seen it talked about as part of 1.4 so probably not the first release.

1 Like

Any news on this feature?

Its 2021 and I still see occasional need for such default generics. any updates on this?

best regards

You don’t need a language feature for that:

class MyClass<T> {

    companion object {
        operator fun invoke() = MyClass<Int>()
    }

}


fun main() {
    val foo = MyClass<String>()
    val bar = MyClass() // this is a MyClass<Int>
}
1 Like

I don’t know Rust or any other language with this feature, but I guess this is not only about initializing and object. You still can’t do e.g.:

interface Foo {
    val myObj: MyClass
}
1 Like

Use a default using a typealias:

class MyClass<T> {

    companion object {
        operator fun invoke() = MyClass<Int>()
    }

}

typealias MyClassInt = MyClass<Int>

interface Foo {
    val myObj: MyClassInt
}

But actually, why do you need that in the first place?

In my case I have a ViewModel Class that accepts an inputDataAdapter so that a ViewModel for a specific view can provide a TypeAdapter from String to “T”. But in some or many cases a simple (String) → String inputDataAdapter could be provided as Default Lambda.

The only way that I figured out is to “dirty” cast to T. If I could provide a Default Type for “T” the dirty cast is no more needed.

class ViewModel<T: DA = String>(
    val inputDataAdapter: (String) -> DA = { input -> input },
)

// instead of

class ViewModel<T>(
    val inputDataAdapter: (String) -> T = { input -> input as T },
)
1 Like

I just needed this avoid changing an entire bass class and violating its encapsulation.

I need to set a default type for var value: T. I can’t use lateinit here, what should I use except of null if I want to make the variable non-nullable?
Found a nice work-around:

public suspend fun <T> Flow<T>.first(): T {
    var result: Any? = NULL
    collectWhile {
        result = it
        false
    }
    if (result === NULL) throw NoSuchElementException("Expected at least one element")
    return result as T
}

You can use some marker object defined above the function like so:

private object EMPTY
public suspend fun <T> Flow<T>.first(): T {
    var result: Any? = EMPTY
    collectWhile {
        result = it
        false
    }
    if (result === EMPTY) throw NoSuchElementException("Expected at least one element")
    return result as T
}

Is this your real case or only an example? In the above code I don’t see a reason to not use the null - it is the best option here.

3 Likes

It’s the Flow original method I found to be usable. You can’t use null as it breaks the needed conditions (you need to use value!! or likewise later everywhere). NULL here is

@JvmField
@SharedImmutable
internal val NULL = Symbol("NULL")
1 Like

There is nothing wrong in using !! in cases it is really needed. In this specific case you actually wouldn’t need to use it, because if (result == null) throw automatically smart-casts to not-nullable. Kotlin is that smart!

I believe the reason what they used NULL in this function is because the flow itself could pass null values, so they needed a way to distinguish situation where we collected null or not collected anything. Alternatively, they could use a separate boolean flag, but such NULL object is often easier to use and even better for performance. Drawback is that we have to use an unchecked cast, which we wouldn’t need if not using NULL.

1 Like