https://kotlinlang.org logo
#reflect
Title
# reflect
z

Zoltan Demant

10/10/2023, 6:09 AM
I would like to remove the type property on this interface and instead figure it out at runtime. Is this possible using reflection, and if so: how?
Copy code
interface Dispatcher<T : Request<R>, R> {

    val type: KClass<T>

    suspend fun dispatch(
        request: T,
    ): R
}
At runtime, I have a
List<Dispatcher<*,*>>
which Id like to turn into
Map<KClass<out Request<*>>,Dispatcher<*,*>>
. I can successfully do this with the type property, but it would be nice to not have to declare type over and over and over again (194 times, and counting).
e

ephemient

10/10/2023, 6:19 AM
sort of…
Copy code
import kotlin.reflect.*

interface Dispatcher<T>
class FooDispatcher : Dispatcher<Int>
class BarDispatcher : Dispatcher<String>

listOf(FooDispatcher(), BarDispatcher()).associateBy {
    it::class.supertypes
        .first { it.classifier == Dispatcher::class }
        .arguments.single().type!!.classifier as KClass<*>
} == mapOf(Int::class to FooDispatcher(), String::class to BarDispatcher())
which can fail in various ways (such as if the inheritance doesn't use
Dispatcher<some concrete class>
or r8 has stripped out metadata)
z

Zoltan Demant

10/10/2023, 6:26 AM
Thanks, thats actually very neat. I think I can work around the issues, all dispatchers are in one module, so I can quite easily instruct R8 to keep their metadata, and all dispatchers follow the same pattern (unless I misunderstand what you mean): its always like this:
Copy code
@JvmInline
value class GetAccount(
    val id: Id,
) : Request<AccountView>
Copy code
class GetAccountDispatcher(
    ...
) : Dispatcher<GetAccount, AccountView>
Will try your solution in a little bit 🙂
e

ephemient

10/10/2023, 6:33 AM
if you have a
Copy code
abstract class AbstractDispatcher<T, R> : Dispatcher<T, R>
class ConcreteDispatcher : AbstractDispatcher<Foo, Bar>()
then my snippet will fail. it's possible to handle, but annoying
if you have
Copy code
class GenericDispatcher<T, R> : Dispatcher<T, R>
GenericDispatcher(...)
then that will fail since generics are erased at runtime
but as long as you use the same pattern, it should be fine. if they're all in one module, consider making it
Dispatcher
sealed, and even adding a test that checks that all of
Dispatcher::class.sealedSubclasses
are ok
z

Zoltan Demant

10/10/2023, 6:45 AM
Fortunately, just concrete implementations - no base classes, generic variants, etc; no plan to have that either. I would use sealed, but they are in different packages 😞 I think Ill be fine given that I do all of this at launch, so Ill just get a crash in my production release if anything is off? 🤞🏽 Ill also have to check how long this takes to perform, Im sort of naively hoping that its just a few milliseconds - which would be okay.
e

ephemient

10/10/2023, 6:49 AM
Kotlin reflection takes a little time to initialize, and Java reflection (which Kotlin reflect uses underneath) on Dalvik has historically been slower than desktop JVMs
at least if it's all within one module, if it does become a problem, you could use annotation processing to generate an extension
Copy code
val Dispatcher<T>.type: KClass<T> @Suppress("UNCHECKED") get() = when (this) {
    is FooDispatcher -> Foo::class
    // etc.
} as KClass<T>
at compile-time
z

Zoltan Demant

10/10/2023, 7:03 AM
Kotlin reflection takes a little time to initialize, and Java reflection (which Kotlin reflect uses underneath) on Dalvik has historically been slower than desktop JVMs
Ah yes, I did not consider this. Maybe an annotation processor is preferrable, Ill check how long it might take to implement that 😄 Ultimately trying to do this to save time, and Im starting to feel pulled in to creating something much larger than the initial problem 😅
e

ephemient

10/10/2023, 7:12 AM
maybe one day we may get https://youtrack.jetbrains.com/issue/KT-13127 and then
Copy code
abstract class Dispatcher<T : Request<R>, R>(val type: KClass<T>) {
    inline <reified T : Request<R>, R> constructor(): this(type = T::class)
}
class FooDispatcher : Dispatcher<FooRequest, Foo>()
will be possible. until then, if you're under time constraints, I'd just leave the
val type: KClass<T>
as-is. it's not broken and doesn't need additional testing
z

Zoltan Demant

10/10/2023, 7:40 AM
Thatd be nice. And agreed 👍🏽 Thanks for all the input!