Typeclass KClass

Hi,
this proposal follow the typeclass on Kotlin.

The class kotlin.reflect.KClass can become a typeclass, ie:

typeclass KClass<T : Any>

For each exsisting type T the Kotlin compiler is already able to define a specific instance of KClass<T>, currenlty it is expressed as T::class.
The goal is to mimic a reified type without requiring the inlining.

A simple example is:

class TypePredicate<T> : Predicate<T> given KClass<T> {
    override fun filter(e : T) = e is T
}

val stringTypePredicate : Predicate<String> = TypePredicate()

I prepared a litte proof of concept for JVM, I reimplemented some reified function using typeclass.
Before each typeclass version a comment explain a possible syntax.

sealed class Shape
data class Box(val side: Int) : Shape()
data class Circle(val radius: Int) : Shape()

fun main(args: Array<String>) {
    val list: List<Shape> = listOf(Box(1), Circle(2), Box(3), Circle(4))
    println("ir typeOf " + typeOf(list))
    println("tc typeOf " + typeOf(list, List::class))
    println("ir filterInstance " + list.filterInstance<Shape, Box>())
    println("tc filterInstance " + list.filterInstance<Shape, Box>(Box::class))
    println("ir newArray " + newArray<String>(3))
    println("tc newArray " + newArray(3, String::class))
}

inline fun <reified T : Any> typeOf(e: T) = T::class

// fun <T> typeOf() given KClass<T> = T::class
fun <T : Any> typeOf(e: T, t: KClass<T>) = t

inline fun <I : Any, reified O : I> List<I>.filterInstance(): List<O> =
        filter { it is O } as List<O>

// fun <I:Any, O: I> List<I>.filterInstance() given KClass<O> = filter { it is O } as List<O>
fun <I : Any, O : I> List<I>.filterInstance(o: KClass<O>): List<O> =
        filter { o.java.isInstance(it) } as List<O>

inline fun <reified T : Any> newArray(length: Int) = arrayOfNulls<T>(length)

// fun <T : Any> newArray(length: Int) given KClass<T> = arrayOfNulls<T>(length)
fun <T : Any> newArray(length: Int, t: KClass<T>) =
        java.lang.reflect.Array.newInstance(t.java, length)

The above code is a simple POC.
For various reasons I not consider the type class as a replacement of reified parameter.

7 Likes

I really like the idea and I have run in some problems which I had to solve passing the KClass instance.
But why would you not just use the reified keyword for those functions as well.
If I understand you right your proposal would allow you to do anything you could do with reified types so why not call them that.

Hi @Wasabi375,
using the reified keyword is probably the most natural choice.
I am glad to share with you some considerations about it, please give me your feedback if you want.

In such case we donā€™t have a reified type and we probably may have some issue about it due JVM type erasure.
At compile time the compiler donā€™t know too much abuot the effective type, we can bypass this issue using reflection.

So it isnā€™t possible use the effective type T, but we can provide the same behaviours to the type T using the typeclass. (we mimic it)

Moreover reifing a type in this way can induce some related effects: reflection may replace bytecode instructions and this can be propagated in ā€˜inline reifiedā€™ funcion called.

I try to explain better:

fun <T> fun1() given KClass<T> = fun2()
inlined <reified T> fun2() = TODO()

To compile this small example the inlined fun2 should work in the same manner of fun1.

For this reasons I consider more adeguate the typeclass solution than the reified one.
In my opinion the use of the keyword reified is a ā€œliesā€.

I totally agree with you, from an implementing point of view. Behind the hood an inline reified type would work differently than a function with a typeclass as an parameter.
But if you look at it from the users perspective they are the same. As far as I understand you should be able to do everything with a reified type, you can do with a typeclass. Therefor I think we should use the same keyword.
Also if you look at the definition of reify, it states

make (something abstract) more concrete or real

So I wouldnā€™t consider it a lie calling both variances reified.

There is although one problem I still see with this. Your POC only works on the JVM. I donā€™t know java script and I have not used Kotlin native yet. For some of those examples you gave there would need to be a Kotlin version. eg:

// fun <I:Any, reified O: I> List<I>.filterInstance() = filter { it is O } as List<O>
fun <I : Any, O : I> List<I>.filterInstance(o: KClass<O>): List<O> =
        filter { o.java.isInstance(it) } as List<O>

// fun <reified T : Any> newArray(length: Int) = arrayOfNulls<T>(length)
fun <T : Any> newArray(length: Int, t: KClass<T>) =
        java.lang.reflect.Array.newInstance(t.java, length)

Those 2 examples would only work for Java. Than there is the problem of all the other collection types. Are there ways to create Lists, Maps, etc using reflection?
I am sure there are ways to do this with Native and I guess it is also possible with JavaScript, but I feel like there are a lot of edge cases that have to be solved.

I guess the Kotlin team also thought about something like this when they designed Kotlin, if they have, why have they not implemented it? ( I hope because it was not high enough a priority :slight_smile: )

1 Like

I respect your point of view, I consider it fully reasonable.

This is a simple POC to show a simple solution, I used reflection but the compiler can produce a better bytecode.

You cannot instantiate a List (interface) in Kotlin, you cannot do this neither in a inlined reified function. This behaviour remains unchanged.

I feel the same suspect but unfortunately I not am smart enough to find it!
Can you produce an example?

I was thinking of the implementations used when you call listOf, etc, so ArrayList, etc but I guess there are ways to solve this.

Not right now. Iā€™ll have a exams next week. Iā€™ll think about it after them. But I feel like they are most probably related to reflection.