Skip to content

Instantly share code, notes, and snippets.

@kyay10
Last active July 30, 2023 20:45
Show Gist options
  • Save kyay10/466212b9fa43823f1dd95c8eee74f7f1 to your computer and use it in GitHub Desktop.
Save kyay10/466212b9fa43823f1dd95c8eee74f7f1 to your computer and use it in GitHub Desktop.
Experiment with typeclasses using context receivers
@file:OptIn(ExperimentalContracts::class) @file:Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
import kotlin.contracts.*
@Target(AnnotationTarget.FUNCTION)
annotation class Concept
inline fun <A, B, R> withContexts(
a: A, b: B, block: context(A, B) () -> R
): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(a, b)
}
inline fun <A, B, C, D, E, F, R> withContexts(
a: A, b: B, c: C, d: D, e: E, f: F, block: context(A, B, C, D, E, F) () -> R
): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(a, b, c, d, e, f)
}
interface Display<in T> {
fun display(t: T): String
}
interface Functor<in FA, out A, out FB, in B> {
fun FA.map(mapper: (A) -> B): FB
}
interface Foldable<in FA, out A> {
context(Monoid<A>)
fun fold(f: FA): A
}
context(Monoid<A>, Foldable<FA, A>) fun <FA, A> FA.fold() = fold(this)
interface Monoid<M> {
infix fun M.combine(other: M): M
val empty: M
}
sealed interface ListFunctor<A, B> : Functor<List<A>, A, List<B>, B> {
override fun List<A>.map(mapper: (A) -> B): List<B> = buildList {
for (item in this@map) {
add(mapper(item))
}
}
companion object : ListFunctor<Any?, Any?>
}
@Concept
fun <A, B> listFunctor() = ListFunctor as ListFunctor<A, B>
open class ListFoldable<A> : Foldable<List<A>, A> {
context(Monoid<A>)
override fun fold(f: List<A>): A {
var result = empty
for (item in f) {
result = result combine item
}
return result
}
companion object : ListFoldable<Any?>()
}
@Concept
fun <A> listFoldable() = ListFoldable as ListFoldable<A>
object StringMonoid : Monoid<String> {
override infix fun String.combine(other: String): String {
return this + other
}
override val empty: String
get() = ""
}
object IntDisplay : Display<Int> {
override fun display(t: Int): String {
return "Display Int $t"
}
}
object StringDisplay : Display<String> {
override fun display(t: String): String {
return "Display String $t"
}
}
context(Display<T>, Functor<FT, T, FStr, String>, Foldable<FStr, String>)
class FunctorFoldableDisplay<FT, T, FStr> : Display<FT> {
override fun display(t: FT): String = with(StringMonoid) {
"Display Functor [" combine t.map { display(it) combine ", " }.fold().removeSuffix(", ") combine "]"
}
}
context(Display<T>, Functor<FT, T, FStr, String>, Foldable<FStr, String>)
@Concept
fun <FT, T, FStr> functorFoldableDisplay() = FunctorFoldableDisplay()
context(Display<T>)
class ListDisplay<T> : Display<List<T>> {
override fun display(t: List<T>): String =
t.joinToString(prefix = "Display List [", separator = ", ", postfix = "]") { display(it) }
}
context(Display<T>)
@Concept
fun <T> listDisplay() = ListDisplay()
@Concept
fun intDisplay(): Display<Int> = IntDisplay
@Concept
fun stringDisplay(): Display<String> = StringDisplay
fun main() = withContexts(intDisplay(), stringDisplay()) {
withContexts(
listDisplay<String>(),
listDisplay<Int>(),
listFunctor<Int, String>(),
listFunctor<List<String>, String>(),
listFunctor<List<Int>, String>(),
listFoldable<String>()
) {
test()
}
}
context(Display<Int>, ListDisplay<String>, ListFunctor<Int, String>, ListFunctor<List<String>, String>, ListFunctor<List<Int>, String>, ListFoldable<String>)
fun test(): Unit = withContexts(
functorFoldableDisplay<List<Int>, Int, List<String>>(),
functorFoldableDisplay<List<List<String>>, List<String>, List<String>>()
) {
with(
functorFoldableDisplay<List<List<Int>>, List<Int>, List<String>>()
) {
println(listOf(1, 2, 3, 4, 5).concatenateItemAndDisplayList(42))
println(listOf(1, 2, 3, 4, 5).map(Int::toString).concatenateItemAndDisplayList("42"))
println(listOf("hello", "world").display())
println(listOf(listOf("he", "l", "lo"), listOf("worl", "d"), listOf()).display())
println(
listOf(
listOf(42, 43, 44, 45), listOf(-42, -43, -44, -45), listOf(42, 43, 44), listOf(-42, -44, -45)
).display()
)
println(listOf(42, 42).display())
println(display(listOf("hello")))
}
}
context(Display<List<E>>)
fun <E> List<E>.concatenateItemAndDisplayList(item: E) = display(this + item)
context(Display<X>)
fun <X> X.display() = display(this)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment