https://kotlinlang.org logo
Title
e

Emil Kantis

05/17/2023, 10:32 PM
Would it be possible to implement reified generics for classes at some point?
e

ephemient

05/18/2023, 3:11 AM
how would you want that to work?
if it were analogous to functions,
inline class Foo<reified T>
but that can't work because you can't inline a class into its callsite
I suppose you could try the template specialization thing that C++ does, but that requires linkers be able to merge inline definitions that were instantiated in different places, which is definitely not how the JVM works
e

Emil Kantis

05/18/2023, 7:42 AM
Unable to come up with a smaller example,so please bear with me.. What I sometimes resort to doing is creating an inline function using reified generics to return an anonymous object implementing an interface. For instance:
inline fun <reified T> stringSerializer(
   crossinline decode: (String) -> T,
   crossinline encode: (T) -> String = { it.toString() },
   nameOverride: String? = null,
): KSerializer<T> = object : KSerializer<T> {
   override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.STRING)

   override fun deserialize(decoder: Decoder) =
      decode(decoder.decodeString())

   override fun serialize(encoder: Encoder, value: T) =
      encoder.encodeString(encode(value))
}
This will then be used like:
object FooSerializer : KSerializer<Foo> by stringSerializer(Foo::FromString)
I think this is kind of hard for people from OO-land and that this would be an easier to entry for them:
abstract class StringSerializer<T>(
   nameOverride: String? = null,
): KSerializer<T> {
   override val descriptor = PrimitiveSerialDescriptor(nameOverride ?: T::class.simpleName!!, PrimitiveKind.STRING) // Cannot use T as reified here
   
   abstract fun decode(value: String): T
   abstract fun encode(value: T): String

   override fun deserialize(decoder: Decoder) =
      decode(decoder.decodeString())

   override fun serialize(encoder: Encoder, value: T) =
      encoder.encodeString(encode(value))
}


object FooSerializer : StringSerializer<Foo>() { 
  override fun decode() = TODO()
  override fun encode() = TODO()
}
So I guess the template comparison makes sense, the inline class would always create specializations on use. I think it would be up to the compiler to generate these specializations during compile-time
e

ephemient

05/18/2023, 8:34 AM
that doesn't work on JVM with separate compilation, there's a reason why generics are erased at runtime :( well, unless you're ok with every call site becoming a different type, which has different downsides
in this case, perhaps simply naming the function like a factory (
inline fun <reified T> StringSerializer(...)
) would make its usage feel more OO?
f

franztesca

05/18/2023, 4:34 PM
What is the reason why reified generics can be only on inline functions? Potentially, the compiler could just add an additional parameter to the function(/class constructor?) that has a reified parameter so that it can be referred by the function/class body. For example:
fun <reified T> example() {
    println(T::class)
} 

fun main() {
    example<Int>()
}
could be compiled to
fun example(clazz: KClass) {
    println(clazz)
}

fun main(){
    example(Int::class)
}
Which is what people end up doing manually right now with:
@PublishedApi
internal fun example(clazz: KClass<*>) {
    println(clazz)
}

inline fun <reified T> example() = example(T::class)
Following the kotlin mantra, I would expect this boilerplate to be removed.
e

ephemient

05/18/2023, 5:39 PM
not everything that can be done with reified types can be done with a class reference
there is this proposal https://youtrack.jetbrains.com/issue/KT-50113/Inline-default-parameters-for-functions which would enable something equivalent to that boilerplate without making reified mean different things in different places
f

franztesca

05/18/2023, 8:33 PM
What wouldn't work with the class reference? The compiler could use the dynamic cast/instance check (at least on JVM) from the class reference. That would also be aligned with lambdas: if you don't inline the function, they are dynamically invoked, if you inline they are not.
e

ephemient

05/18/2023, 11:02 PM
arrayOf<T>()
needs a reified type, not just a class reference
typeOf<T>()
exposes generic type parameters which are lost with a class reference