Skip to content

Instantly share code, notes, and snippets.

@Sintrastes
Last active November 21, 2021 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sintrastes/3920cf4efcea4c933b19382222e59ce2 to your computer and use it in GitHub Desktop.
Save Sintrastes/3920cf4efcea4c933b19382222e59ce2 to your computer and use it in GitHub Desktop.
Higher kinded types examples in Kotlin.
//
// Example of what non-"Type" kinded HKTs might look like in
// Kotlin with support for HKTs via a compiler plugin
//
sealed interface Nat<N: Nat<N>>
data class S<N: Nat<N>>(val pred: N): Nat<S<N>>
object Z: Nat<Z>
// F here inferred to have kind "Nat -> Type -> Type"
interface SizedFunctor<F> {
fun <A, B, N: Nat> F<N,A>.fmapSized(f: (A) -> B): F<N,B>
}
// Example
sealed interface Vect<N: Nat<N>, A> {
class Nil<A>: Vect<Z, A>
class Cons<N: Nat<N>,A>(val head: A, tails: Vect<N,A>): Vect<S<N>,A>
companion object { }
}
fun Vect.Companion.sizedFunctor() = object: SizedFunctor<Vect> {
override fun <A, B, N: Nat<N>> Vect<N,A>.fmapSized(f: (A) -> B): Vect<N,B> {
when (this) {
is Nil<A> -> Nil<B>()
// Probably some type casts needed here to actually get this to compile.
is Cons<*,*> -> Cons(f(this.head), this.tail.fmapSized(f))
}
}
}
//
// Some simple examples of what compiler-plugin supported
// HKTs might look like in Kotlin -- with some subtyping examples.
//
//
// Note: Kind for F here is inferred -- not sure how feasible/useful it would be to allow the user to
// explicitly specify kinds. How would we seperate subtyping annotations from kinding annotations?
//
// Probably best to avoid the Scala-esque F<_> notation.
//
interface Functor<F> {
// Use fmap here to avoid name conflicts with most container classes
// which already have a "map" function.
fun <A,B> F<A>.fmap(f: (A) -> B): F<B>
}
// Example instances
@Given
fun Flow.Companion.functor() = object: Functor<Flow> {
override fun <A,B> Flow<A>.fmap(f: (A) -> B): Flow<B>
= this.map(f)
}
@Given
fun StateFlow.Companion.functor() = object: Functor<StateFlow> {
override fun <A,B> StateFlow<A>.fmap(f: (A) -> B): StateFlow<B> {
val flow = this
return object: Flow<B> by (flow.map(f)), StateFlow<B> {
override val value: B
get() = flow.value.let(f)
override val replayCache: List<B>
get() = flow.replayCache.map(f)
}
}
}
// Use-sites
val myStateFlow = MutableStateFlow(42)
// F inferred to be StateFlow in fmap<F>
val myMappedStateFlow: StateFlow<String> = myStateFlow.fmap { it.toString() }
// Explicitly use the Flow instance instead of the StateFlow instance.
val myMappedFlow: Flow<String> = myStateFlow.fmap<Flow> { it.toString() }
//
// Example looking at what a profunctor typeclass would look like in
// Kotlin with compiler plugin support for HKTs.
//
interface Profunctor<P> {
fun <A,B,X> P<A,B>.imap(f: (X) -> A): P<X,B>
fun <A,B,X> P<A,B>.omap(f: (B) -> X): P<A,X>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment