Kotlin generics with heterogenous containers

40 Views Asked by At

Suppose I have a heterogeneous map container, where keys contain type information on values:

interface Container<K : Container.Key<*>> {
    interface Key<V>
    
    interface Entry<K : Key<V>, V>
}

I want to have a get() function akin to

fun <V> get(key: Key<V>): V?

but also restrict this function to our type parameter K - The idea is that a class that implements Container might also have their custom key type Container.Key<V>, and we want the input parameter to get() be Container.Key<V> as a result.

Is this expressible in Kotlin?

1

There are 1 best solutions below

0
Sweeper On

No, this is not possible (yet).

You essentially want:

fun <V, TKey: K & Key<V>> get(key: TKey): V?

That is, get should take a thing that is both a K, and a Key<V>. Unfortunately, this kind of intersection type is not available yet.

Kotlin only supports intersection types of the form T & Any, where T is a nullable type parameter, to express "the non-nullable version of T".

There is a lengthy discussion here about adding better intersection type support.

So for now, you can only enforce TKey: K & Key<V> at runtime. Take in two parameters - a K and a Key<V>. Enforce that they are the same object.

// users would call this by passing the same thing twice
fun <V> get(key1: K, key2: Key<V>): V?

In the implementation you should check key1 === key2.

Note that we cannot check that a key is indeed a Key<V> and not Key<SomethingElse> at runtime, because V is erased. You can take a KClass<V> to work around this, but that doesn't work well if V is itself parameterised.

If that is fine for you, and your Key interface has a way to tell you if it is a Key<V>, given a KClass<V>, you can use a signature like this instead:

fun <V> get(key: K, clazz: KClass<V>): V?
// in the implementation, check if key is Key<V>, using members of Key<*> and clazz