Last active
July 30, 2023 20:45
-
-
Save kyay10/466212b9fa43823f1dd95c8eee74f7f1 to your computer and use it in GitHub Desktop.
Experiment with typeclasses using context receivers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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