Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does Kotlin's type reification make possible that is not possible in Java or Scala?

I am most familiar with Java type erasure (with all its issues and benefits). I have some limited exposure to the expanded possibilities with Kotlin's type system, but I don't have a clear understanding of how type reification works on an erasure-oriented JVM. What is type reification, how does Kotlin make it possible on the JVM, and how does this differ from Java's type erasure and Scala's sophisticated type system?

like image 787
Nate Vaughan Avatar asked Dec 17 '17 04:12

Nate Vaughan


2 Answers

What is reification?

Type reification is one of Kotlin's tricks. It happens only in inlined generic functions, if you declare the generic parameter as reified.

Since it's inlined, the generic parameter can be a concrete class, instead of just a compile-time type information.
You can do something impossible in Java like:

instanceof

You can now use instanceofs (in Kotlin, iss):

inline fun <reified T> f(a: Any) {
    if (a is T) println("Hi!")
}

This is obviously impossible in Java.

reflection

You're possible to get the java java.lang.Class<T> instance from the generic parameter now.

inline fun <reified T> f(a: Any) {
    println("Hey! my class is ${T::class.java}!")
    if (a.javaClass == T::class.java) println("Hi!")
}

Also, KClass as well:

inline fun <reified T> f(a: Any) {
    println("KClass: ${T::class}")
}

You can create instances with the empty constructor:

inline fun <reified T> f(a: Any) {
    val o: T = T::class.java.newInstance()
}

calling other reifieds

Only reified generic parameter is possible to be passed to other reified functions.

inline fun <reified T> f(a: Any) {
    g<T>(a)
}

inline fun <reified T> g(a: Any) {
    if (a is T) println("Bingo!")
}

This is impossible in Kotlin:

inline fun <reified T> f(a: Any) {
}

fun <T> g(a: Any) {
    f<T>(a) // error
}

shortcomings (edited)

If you use other languages to invoke a reified inline function in Kotlin, the function parameter will be java.lang.Object.

You can't use other languages to invoke a reified function.

Like, if we have a reified function in A.kt:

inline fun <reified T> f(a: T) = println(T::class.java)

And get it using reflection (it will be compiled as private):

Method method = AKt.class.getDeclaredMethod("f", Object.class);

This code will successfully run without exceptions.
But you can't invoke it (I didn't read the generated bytecode carefully, sorry) due to it's implementation:

private static final void f(Object a) {
  Intrinsics.reifiedOperationMarker(4, "T"); // I didn't see
  // the implementation of this line, so I thought it's
  // possible to call it in other languages
  Class var2 = Object.class;
  System.out.println(var2);
}

Look at the comment. And look at the definition of reifiedOperationMarker:

public static void reifiedOperationMarker(int id, String typeParameterIdentifier) {
    throwUndefinedForReified();
}

And it will throw an UnsupportedOperationException.

Conclusion: reified can only be used in Kotlin.

about scala

It's really difficult to say whether Kotlin or Scala is better, because Scala's has more ways to get type information at runtime.

Alexey Romanov said that Scala can but Kotlin can't:

using ClassTags in a recursive function

I think this can be solved by using functions inside functions:

inline fun <reified T> g(a: Any): Int {
  var recur: ((Any) -> T)? = null
  recur = { recur!!.invoke(it) as T } // use T is possible here
  return recur(a)
}

Note that this is only an example that syntactically correct.
It's infinite loop and unnecessary cast, of course.

He also said:

storing them in collections and using them to call ClassTag-using functions later.

This is a true problem, because this needs noinline lambdas, while Kotlin's reified is based on inline.

like image 195
ice1000 Avatar answered Nov 12 '22 21:11

ice1000


When Kotlin inlines a generic function, it naturally substitutes the type parameters by the type it was called with. E.g. with inline fun <T> foo(x: T) = ...

foo(File("."))

becomes

val x = File(".")
// body of foo with File used everywhere T was

What reified does is just to allow using operations in body of foo which will only make sense after this substitution but are illegal for non-reified type parameters, such as T::class.

The relevant Scala feature is ClassTag/TypeTag, not the "sophisticated type system". Effectively, it automates passing the Class<T> (or TypeToken<T>) as an argument, which can be done manually in Java and often is. Note that this is a completely different approach than reified.

I don't think there's anything which reified does which isn't possible in Scala, but the advantage of the Kotlin approach is more natural syntax: e.g. in in Scala you can't just write classOf[T] in a ClassTag-using method like you would classOf[File].

OTOH, Scala allows things which aren't possible with reified, e.g.:

  1. using ClassTags in a recursive function

  2. storing them in collections and using them to call ClassTag-using functions later.

like image 26
Alexey Romanov Avatar answered Nov 12 '22 21:11

Alexey Romanov