Typeclass KClass


#1

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.


Reified generics at class level with TypeTag
Reified generics in interface
#2

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.


#3

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”.


#4

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: )


#5

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?


#6

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.