Higher kinded types in Kotlin
Higher-kinded-types are a type of generic programing feature that allows for generic parameters like F
to stand for not just types
like Int
or Double
-- but types which take other types as arguments. The most common example often encounted is containers, like F = List
, F = Sequence
, or F = Map
.
Kotlin does not natively support such generic type parameters -- if you don't believe me, just try plugging just List
(not List<A>
, List<Int>
, or List<*>
-- just List
) into something expecting a generic parameter.
Iodine makes use of these higher-kinded-types by making use of a lightweight encoding of this feature, as some features (such as making use of an interface I
to define the type of interactions with a component) would be difficult to implement in a clean, declarative way without them. Additionally, Iodine makes use of some more experimental/advanced features by making use of this encoding.
To allow for the highest level of interoperability with other frameworks using an encoding of Higher-kinded types in Kotlin/on the JVM, Iodine currently makes use of the kindedJ library in it's encoding.
The core ideas of this approach are that:
Hk<Fw,A>
is a dummy "tagging" interface used to represent the higher-kinded applicationF<A>
.- The generic type
F<A>
should extend the interfaceHk<Fw, A>
. Due to sub-typing, this means that we can use anF<A>
wherever anHk<F.W, A>
is expected in an API. Fw
is a "witness" for the higher-kinded typeF
. In other words, a dummyobject
used to representF
as an argument toHk
(remember -- we can't useF
itself, which is the whole point of this encoding).- There is a "witness function",
Hk<Fw,A>.fix(): F<A>
that lets us do the conversion the other way around. This requires type-casting, but should be safe as long as for eachFw
, there is only oneF
that implementsHk<Fw, A>
.
For example, to represent MyType
as a higher-kind with the witness MyType.W
:
data class MyType<A>(val someValue: A, val someOtherValue: A): Hk<MyType.W, A> {
// Witness for MyType
object W
}
@Supress("UncheckedCast")
fun Hk<MyType.W, A>.fix(): MyType<A> {
return this as MyType<A>
}