Can generic parameters be reified at class level?

Note: This proposal has nothing to with the currently experimental inline classes feature, even though I’ll use the term here with a different meaning. If this feature gets considered, it’ll have to be named something else since ‘inline classes’ is taken.

Is it possible to reify a generic parameter at class level by inlining a whole class?

Example:

[inline/some other keyword] class Something<reified A> {

// Some functionality

// Usage of the reified parameter
fun returnSomething(value : Any) : Something<A> =
    if (value is A) // type check possible with reified parameter
        value
    else
        // Some other way to obtain Something<A>

}

// Usage of the class:

val someString = Something<String>();

// Generated code:

class Something$String {

fun returnSomething(value : Any) : Something$String = // Type parameter replaced
    if (value is String)
        value
    else
        // Some other way to obtain Something$String

}

I understand that this could increase the class count significantly, especially for more than one type parameter. Another problem would be if the class with the generic type parameter is declared in some library and used (i.e. supplied with the type parameter) elsewhere.

The solution to the former is that the feature would obviously be optional, so developers could be told of the implications, and then they may use it if they can accept the large class count (or if they aren’t using it with too many type parameters)

The solution to the latter is to produce two versions of the class in the bytecode. One would be a normal generic class with erased type parameters, usable from Java (as is done with inline functions). The second could be a template class especially marked for Kotlin code somehow, and then when someone imports the library and uses the class, his project could inline only the type parameters he has used in his code, e.g. if he has used Something<String>, Something<Int> and Something<Double> only, then only Something$String, Something$Int and Something$Double, are generated, but no others.

I suppose it could also be possible to have (as with inline functions) some reified type parameters and some not, so: [inline/other keyword] class Somethings<reified A, B>, if used as val someStringAndInt = Something<String, Int>, would result in the generation of the class Something$String<B> with the second parameter erased as usual.

I have no idea whether this is feasible or not, so I thought I’d find out. I’ve also filed an issue at YouTrack: KT-33213.

1 Like

You can reify type parameters even in Java, which is done by Guava in its TypeToken class - classes that extend a generic class with fully specified parameters bake in those parameters in a reified fashion. So TypeTokens are constructed new TypeToken<Map<String, UUID>>() {}, or in Kotlin object: TypeToken<Map<String, UUID>>() {}. I do not think there would need to be any special syntax, for the same reason there does not need to be any special syntax for lists - a top-level function works just fine, as in the case of inline fun <reified T> typeTokenOf(): TypeToken<T> = object: TypeToken<T>() {}. And you could of course make a companion object { inline fun <reified T> invoke() } if you want it to look like a constructor.

Hi @mshere96,

Library L1 defines Something<R>.
Library L2 defines privately Something<String> and it is compiled as Something$L2$String.
Library L3 is written in Java and use Something<String> as is, with erased generic.
Then my application define privately Something<String>, my application cannot use Something$L2$String because it is private and it is not part of public interface, so we have to define Something$App$String.
Both Something$L2$String and Something$App$String implements and reifies Something<R> but it is not possible to cast one to other.

Library L1 defines a method to consume Something<R>. This method must accept both L2 and App reified versions and L3 erased version, so this method have to accept the erased version (this is a common supertype).

We have generated a lot of classes but we must continue to use the erased version,
what issue you wish to solve?

1 Like

Of course, you can always do that. But that means you have to plug in the actual types for every type you want put in. There are two issues here:

  1. The point of my suggestion is to reduce the amount of code that needs writing. I’ll give a concrete example from something I’m currently working on:

I have a ConcurrentHashMap<String, Any?> that is supposed to hold (boxed) instances of many different types. To avoid casting the output every time, I’ve written an inline fun <reified T> ConcurrentHashMap<String, T>.getTyped(key : String) that takes a type parameter, attempts to cast the value to a key and if not possible (or if there isn’t any value corresponding to the key) returns null. Now I’ve a base generic class AbstractDelegate<T> which is supposed to have a function fetch() which extracts instances of type T from the map and does something with them (if not null), which means calling getTyped with T. However, AbstractDelegate<T> doesn’t actually have T. So what that means is that I have to write an additional property abstract val fetched : T? and override it in all the implementation that provide the T like so: override val fetched get() = map.getTyped<Int>(key). This might seem like a minor problem but here I have about a hundred implementations with different Ts. I’d appreciate if the language could do this for me (I actually come from C# so I’m used to the language doing it; guess that’s my limitation)

  1. Inline reifying top level or companion object functions doesn’t change the fact that the methods of a class don’t have access to the type parameter declared in a class. I understand that reified generics is big problem with JVM and seems to have no easy solution, but I just wanted to give my two cents and get feedback on whether this approach could go anywhere.

I hadn’t actually considered that, but could it be solved by having the generated classes just inheriting from Something? As in Something$L2$String : Something<String> and Something$App$String : Something<String>? Then they’d both be assignable to Something and thus both usable in the top-level function of L1.

One thing needs bearing in mind, which is that Kotlin can’t do anything about erased types on JVM in Java. But it can (and does) make the lives of Kotlin developers easier. The inline reified functions do just that: they don’t help anyone developing in Java using libraries defined in Kotlin, but they do a lot for people developing in Kotlin using libraries defined in Kotlin.

As for the private case, then I guess a provision could be made that if a type inlined class has been publicly defined and generated in a library I’m consuming, my own code shouldn’t generate another version, i.e. if L1 defines Something<T>, and L2 defines (publicly) Something<String> which gets generated as Something$L2$String, and then in my App I import both L1 and L2, and use Something<String> then my code shouldn’t generate Something$App$String, since Something$L2$String is already in scope, so that’ll be used instead. Bear in mind that being generated, Something$L2$String and Something$App$String would have exactly the same body, that of Something<T> with every occurrence of T replaced with String. However, if L2 defines Something$L2$String privately, then it shouldn’t be an issue for my code any way, as they’ll at least be assignable to each other, both being descended from Something<String>.

Hi @mshere96,

this could be done, but in such case this class become a part of the public ABI, so the compiler have to generate it even it is not used anymore, so I shall discard this path.

Instead many projections of the same class exposes to performance problems (CPU L1 cache will contains a lot of duplicated code) and may become too expensive for Android application (total method count is limited).

I don’t think that this is a bad idea, however JVM type erasure is a big obstacle.

Currently we are discussing a similar proposal

I proposed an enhancement to emulate reified type, see here

2 Likes

Yes, I’ve seen this KEEP. It’s a really good idea. Would really simplify dealing with and reasoning about dependencies.

As for the multiple projections of the same class, that should only becomes a problem when the same class with the same type is projected multiple times for a single codebase, i.e. Something$L1$String and Something$L2$String, with both in scope for let’s say L2. If each class is projected only once, it shouldn’t be that much of an issue. Languages with true generics such as C# do this. List<String> and List<int> are two completely different classes in C#. But we could refine my suggestions above like this:

  1. Only generate a projection if the class is actually instantiated for the type in the codebase. If Something<String> is instantiated, and Something<Int> is not, generate only Something$String. Note that criteria is instantiation, not just use, which could also refer to inheriting, as in the next point.

  2. Don’t generate projections for the base class if another class inherits from it with a type specification, i.e.: SpecialSomething : Something<String> shouldn’t generate SpecialSomething : Something$String, but instead SpecialSomething : Something<String> with all methods containing occurrences of T overridden (even if they aren’t open; this should be doable for the bytecode) and occurrences of T replaced with String. Also, SpecialSomething<T> : Something<T> with the subsequent instantiation of SpecialSomething<String>() should only generate SpecialSomething$String : Something<String> and not Something$String.

  3. Ensure that only one projection for a class for a type exists in any particular scope (public or private). This should be easy to do. Check for existing projections, and if any, use them rather than generating any more.

By the way, thanks for using the word “projection”. I didn’t really know what to call it, so I was using the phrases “inlined class” and “generated class”. Projection works very well.

While it is not currently possible to reify generic parameters at class level — like the OP mentioned, it can actually be done in methods and functions.
Since operators are functions, you can create a fake constructor as the invoke operator of the companion object of a class, like follows:

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

class Something<T : Any> @PublishedApi internal constructor(
    private val classT: KClass<T>
) {

    companion object {
        @JvmStatic
        @JvmName(name = "create")
        inline operator fun <reified T : Any> invoke() =
            Something(T::class)
    }

    fun <U> safeCast(value: U) =
        classT.safeCast(value)

    inline fun <U : R, R> isInstanceOrElse(value: U, otherwise: () -> R) =
        safeCast(value) ?: otherwise()
}

fun doSomething() {

    val someString = Something<String>()
    val value = someString.isInstanceOrElse(value = "Hello world") { "I'm not a string" }

    println(value)
}
1 Like

What is the @JvmName annotation used for, seems function with reified method cannot be accessed from java

1 Like

The @JvmName annotation can be used to specify that a different name be used in the JVM class file for a Kotlin symbol. That’s why it cannot be accessed from Java — it is a Kotlin annotation.
This way, a Kotlin symbol can have a different name on the JVM than it is accessed from within Kotlin source code.

the codesnap seems still using unchecked cast, so can we say it’s not reified at class level, it’s just refied in the overloading invoke method()?
Besides, without the reified and operator keyword, the invoke method could be accessed in java with Something.create()

Where is the unchecked cast?
Also, isn’t the create method still accessible from Java? That’s why I used the @JvmName annotation.

1 Like

the safeCast() method of KClass is also unchecked cast, see the source of KClass.
And the create method with refied keyword isn’t accessable from Java, tried with AndroidStudio 2021.2.1.

The way safeCast is implemented is irrilevant — the cast is safe in regards to the type system.
Sure, it’s not reified at class-level (I’ve never claimed otherwise), as that’s impossible in that Kotlin does not provide support for it.
As to the method being unaccessible from Java, I checked the generated bytecode, and –as I was suspecting– the reified modifier makes it synthetic. Anyway, since Java does not provide support for reifying generics, how’d you expect Java code to instantiate the class? Actually, you can invoke the constructor (since I made it @PublishedApi) passing the KClass object.

1 Like

This seems like a continuation of Reified generics - #2 by abreslav

Are there any plans to do this in Kotlin, now that the language has been catching on so well?

Seems it’d be very useful to automatically/at-compile-time do all of the reification that can be done through TypeToken