Kotlin reified type parameter can't be used as type parameter in body of function
Asked Answered
S

4

5

A reified type parameter in Kotlin prevents type parameter erasure and allows the type parameter to be known at run-time. This allows the following code to compile and run as expected:

inline fun <reified T> isA(value: Any) = value is T

However, when I try to use "T" as a type parameter instead of standalone I get a message that it is an erased type. This is demonstrated by the following code that is for illustrative purposes only:

inline fun <reified T> isListOfA(name: String): Boolean {
    val candidate = Class.forName(name)
    return candidate is List<T>
}

Is this due to a technical limitation? If so, what is that limitation?

Sachsen answered 18/7, 2017 at 17:57 Comment(1)
This isn't a problem with reification. You can't even do canditate is List<String>.Middling
S
9

Evidently I did not formulate my question appropriately to get an answer of the form that I wanted. Most of the answers here are some variation of "because you can't do that in Java". Well, you cannot do x instanceof T in Java, either but you can do x is T in Kotlin. I am looking for the underlying practical roadblock not the Java rule. Rules are made to be broken, after all.

From my comment on the first answer here, the reformulated question is: if objectref is T can be made to work in Kotlin by some mechanism X why can't objectref is SomeClass<T> be made to work by that same mechanism?

tl;dr answer: Because there will be no Class object for SomeClass<T> at run-time.

Longer answer: First we must understand mechanism X, which is to generate an instanceof bytecode instruction for is T. This instruction takes objectref and the name N of some class C, where N is determined from context by the compiler. At runtime, the class C derived from N will be used to evaluate the objectref is T expression. In order for this evaluation to occur the class object for C must be instantiated. So to use this same mechanism for objectref is SomeClass<T> then N would be SomeClass<T>. Due to type erasure, there will not be a class object for SomeClass<T> so it is not possible to generate the needed instanceof instruction and thereby apply the same mechanism. In addition, the instanceof instruction cannot take a name of the form SomeClass<T>. Therefore, if objectref is SomeClass<T> is to work, some other mechanism Y must be found and implemented in Kotlin. Such a mechanism may or may not exist.

I know that some may say that this is the same thing as some of the other answers. However, for better or worse my learning style is to understand how things work down on the metal, and then synthesize this against the abstract model. In this case is the Java Generics notion of erasure is the abstract model (or part of it). Really, "erasure" feels squishy to me unless I understand at least one way that it is realized in a working implementation.

Sachsen answered 19/7, 2017 at 20:12 Comment(0)
T
4

The technical limitation that prevents you from doing that is generics type erasure on JVM. Basically, at runtime an object of a generic type List<T> becomes just a List that works with objects: it's only at compile-time that the type safety is checked for assignments and function calls. The actual type parameter T is there only during compile time and then gets erased. It cannot be restored at runtime (at least for now: there is Project Valhalla that might introduce runtime reified generics for JVM one day).

In a non-inline Kotlin function (and with a non-reified type parameter), you could not even do the first kind of the check, value is T, because an ordinary type parameter would get erased as well.

With reified type parameters, the function body gets inlined at its call sites, with the actual (or inferred) type parameter substituted for T: when you call isA<String>("abc"), the call site will have the bytecode with the instanceof check for String.

But even with reified type parameters, you cannot introspect the generic types: you can check that something is List<*> but not that something is List<String>: the type argument is not stored anywhere at runtime.

Also note that isA<List<String>>(listOf(1, 2, 3)) will return true. That's how this odd case is handled in Kotlin: only the non-generic part of the type can be actually checked at runtime, and so it is.

Transubstantiate answered 18/7, 2017 at 18:24 Comment(0)
B
1

there is no way to do that in Kotlin since Java erase the generic type parameter T into Object/upper-bounded type at compile-time.

The first approach can working it is because value is T is inlined into call-site function with reified type, for example:

//val is_string = isA<String>(1) // inline into the call-site function as below:


val i:Int = 1
//                   v--- the actual type argument is inlined here
val is_string = 1 is String
Ballplayer answered 18/7, 2017 at 18:7 Comment(2)
Yes, I get that the actual type argument will be inlined for "is T" as "is String". If that is possible why is it not possible to inline it for "List<T>", e.g. "List<String>"? That is the question I am asking.Sachsen
@roobyroo No, you can't, due to the type erase occurs at compile-time, so java don't support reified generic type at all.Ballplayer
L
1

Parameterized types are always erased at runtime. So you can check that a value is a T instance but not a T<V> instance, no matter if T and V are reified or hard-coded.

However, even if that was possible, your example code does not make sense because it checks if the type with that name is an instance of List, instead of checking if the type with that name is the expected List type.

If you have an instance of an object and want to check that it's a List only containing items of the expected type, you can still write something like this:

inline fun <reified T> isListOfA(instance: Any)
    = instance is List<*> && instance.all { it is T }
Laccolith answered 18/7, 2017 at 18:51 Comment(1)
Note: this isListOfA check can bite: consider a read-only view of a MutableList<Any> that contained only Strings at some point and thus passed isListOfA<String>(list) check, but later an Int is added whereas you already passed the list to some code that expects all the items to be Strings and therefore fails with a ClassCastException.Transubstantiate

© 2022 - 2024 — McMap. All rights reserved.