Reified type parameters through inline constructors

Hello everyone.

There are situations where it is useful having a class with a generic type parameter that holds some kind of information or has some kind of behaviour related to the generic parameter, like being able to produce the class name or create a new instance of the generic type.
Because, currently, kotlin does not support reified type parameters for classes, a common solution for this problem is to pass a class object with the same type as the generic type to the constructor. To illustrate this idea, consider the following class. Bear in mind that this is a toy example and it is simple so that it is easy to understand.

open class A<T>(private val type: Class<T>) {

    val name: String
        get() = type.name

    fun newInstance(): T = type.newInstance()
}

If we need to subclass the previous class we would do something like the following:

class StringA : A<String>(String::class.java) {

    fun aMethod() {
        // code
    }
}

Notice that we have to reference the String class twice: once as type parameter and again to get its corresponding java Class instance, even though, we know that the only Class instance that is reasonable to be passed to the constructor has to be the String Class instance.

It would be nice if we instead simply subclass A like this:

class StringA : A<String>() {

    fun aMethod() {
        // code
    }
}

To allow this, i suggest the implementation of inline constructors in kotlin that would allow reified type parameters to be passed to the constructor.
Going back to class A, if it had inline constructors, it could be something like this:

open class A<T>(private val type: Class<T>) {

  inline constructor<reified T>() : this(T::class.java)

  val name: String
      get() = type.name

  fun newInstance(): T = type.newInstance()
}

class StringA : A<String>() // subclassing using inline constructor

It is important to notice, that for this to work, the inline constructor must reference a “concrete” constructor for which it will be inlined.
Also, “reified T” in the constructor should be a type parameter of the constructor, however, and considering that, currently, kotlin does not support type parameters on constructors, this could reference the class generic type parameter.

What do people think about this?

4 Likes

I solved situation like this with a create / build function on the companion object instead of logic in constructor. I’m not sure if this is a “good” approach in general, to weaken some “constraints” in object creation.

1 Like

Let’s say it this way. Such a constructor could not normally be used by an inheriting class. An inline factory function seems appropriate here although one needs to be written for each subclass.

@pdvrieze, @werrenmi
If all we need is to create instances of a particular class, then yes, an inline create / build function / method is more than enough, but the idea here is to enable reified type parameter in places where only a constructor is allowed and you couldn’t use a function / method, like in my StringA subclass example.

PS: I fixed the code formatting. Before you couldn’t see the <String> in some places. Now you can. I hope that clarifies my idea.

1 Like

I see your point. However I’m not sure about it. In most cases subclassing fairly infrequent (where creating new instances is not). This is one of those features that struggle with the -100 points rule. As in every feature added to the language creates a cost to the language (evolution) and its use. The feature is worthwhile, but there are simple alternatives that are not unduly burdensome.

As an example of possible issues/cost, how would it interact with pattern matching? A feature that is certainly under consideration (even in Java). To push the feature you should probably identify use cases where the feature has clear benefits.

IMHO this can be solved in most cases with either:

  • a “companion” inline function, with the same name as the class, when the type parameter is to be chosen by the user (Foo)
  • a constant argument, when the type parameter is fixed by the subclass (StringFoo)
@Suppress("FunctionName")
inline fun <reified T> Foo() = Foo(T::class.java)

open class Foo<T>(private val type: Class<T>) {
  val name: String get() = type.name
  fun newInstance(): T = type.newInstance()
}

class StringFoo : Foo<String>(String::class.java)

fun main() {
    println(Foo<Int>().name)   // java.lang.Integer
    println(StringFoo().name)  // java.lang.String
}
1 Like

“Such a constructor could not normally be used by an inheriting class”.

Why?

Looking back, this is when using a factory function instead of an actual constructor. For inheritance you must use an actual constructor.

This is a very old topic, and nobody mentioned this, but if we need to do this for subclassing, then in many cases and specifically in the example provided by the OP, we don’t even need to reify T. It is already known at the runtime, and can be acquired with a little bit of reflection voodoo:

fun main() {
    val o = StringA()
    println(o.name) // java.lang.String
}

class StringA : A<String>()

open class A<T : Any> {
    private val type: KClass<T> = this::class.supertypes
        .single { it.classifier == A::class }
        .arguments[0].type?.classifier as KClass<T>

    val name: String
        get() = type.jvmName
}

I skipped some corner cases and error handling for simplicity.

This approach won’t work if T is provided at the use-site, e.g.: val o = A<String>() or if the subclass is also generic and also instantiated like above. For these cases we actually need a reified factory function. Reified constructor could potentially fill the gap between different approaches.

1 Like

@broot, the original intent was to avoid using reflection altogether and a have a simple solution to this, but yes, what you have shown is a reasonable workaround.

To clarify, the reason that i did not insist on this, back then, was that the actual problem i had could be solved by another solution, even though it was “less elegant”, and i didn’t have other use cases that were convincing enough to push this. Besides, i have come to know about the usage of the inline keyword in array class constructors in kotlin’s standard library which are processed in a special way by the compiler and that makes it more difficult for my idea to be implemented.

What do you mean exactly by “reflection”? Using KClass/Class for anything is essentially using the reflection API. Also, I wouldn’t call this a workaround. This is actually the direct / “official” way how we acquire a type parameter of a class. The only reason we don’t do this very often is type erasure, which makes this not very useful in many cases. But of course, something like T::class would be much simpler, even if underneath it would do something similar to my above code.

After using Kotlin for many years, I’m not anymore entirely convinced the current solution of reified types through inlining is the best one. It is complicated, sometimes confusing and it can’t be used in many cases. For example, we need to call a function that requires a KClass and we have KClass as variable, but we can’t anyhow call it, because it acquires KClass through inlining/reified. Pretty annoying. Constructors is yet another example - we can’t inline constructors, so this is not possible. As you mentioned, Kotlin authors even applied some kind of a hack for the stdlib and created an inline constructor for Array, which is normally not supported by the language. If the language missed some gaps that made impossible to implement the stdlib, then I guess we can expect it will miss same gaps for developers as well.

I wonder if it wouldn’t be simpler to just pass KClass/KType as an additional, hidden parameter. reified on a function type parameter would add a parameter to it. reified on a class type param would add it to constructor and create a property for it. This seems much simpler approach to me and it can be used everywhere. On the other hand, I’m pretty sure Kotlin authors considered this option and for some reasons they decided to go with inlining. Maybe they didn’t like the idea that functions have different signatures than it seems from the source code. I don’t know.

1 Like