Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple "algebraic effects" implementation on Kotlin
/*
* https://overreacted.io/algebraic-effects-for-the-rest-of-us/
*/
private val effectsStack = ThreadLocal<EffectsFrame>()
fun <T> perform(effectKey: Any): T {
var frame = effectsStack.get()
while (frame != null) {
@Suppress("UNCHECKED_CAST")
val effect = frame.effectsSet.findEffect(effectKey) as EffectsSet.Effect<Any, *>?
if (effect != null) {
@Suppress("UNCHECKED_CAST")
return effect(effectKey) as T
}
frame = frame.previous
}
throw RuntimeException("Effect not found")
}
class EffectsSet
{
abstract class Effect<in K, out R>(private val code: (K) -> R) {
abstract fun match(key: Any): Boolean
operator fun invoke(key: K): R = code(key)
}
class ValueEffect<K, R>(private val key: Any, code: (K) -> R) : Effect<K, R>(code) {
override fun match(key: Any): Boolean = this.key == key
}
class ClassEffect<K, R>(private val keyClass: Class<K>, code: (K) -> R) : Effect<K, R>(code) {
override fun match(key: Any): Boolean = keyClass.isInstance(key)
}
private val list = mutableListOf<Effect<*, *>>()
fun findEffect(key: Any): Effect<*, *>? = list.first { it.match(key) }
fun <R> effect(key: Any, code: (Any) -> R) {
list += ValueEffect(key, code)
}
fun <K, R> classEffect(keyClass: Class<K>, code: (K) -> R) {
list += ClassEffect(keyClass, code)
}
inline fun <reified K, R> effect(noinline code: (K) -> R) {
classEffect(K::class.java, code)
}
infix fun run(code: () -> Unit) {
val previous = effectsStack.get()
effectsStack.set(EffectsFrame(previous, this))
try {
code()
} finally {
effectsStack.set(previous)
}
}
}
private class EffectsFrame(
val previous: EffectsFrame?,
val effectsSet: EffectsSet
)
fun withEffects(block: EffectsSet.() -> Unit): EffectsSet {
val frame = EffectsSet()
block(frame)
return frame
}
//--------------------------------------------------------------------------------
data class User(val name: String?)
{
val friendNames = mutableListOf<String>()
override fun toString(): String = "User: $name, friends: $friendNames"
}
fun User.getName(): String = name ?: perform(AskName(this))
val arya = User(null)
val gendry = User("Gendry")
fun makeFriends(user1: User, user2: User) {
user1.friendNames += user2.getName()
user2.friendNames += user1.getName()
}
class AskName(val user: User)
fun main() {
withEffects {
effect("askName") {
"Arya Stark"
}
effect { eff: AskName ->
if (eff.user === arya) "Arya Stark" else "???"
}
} run {
makeFriends(arya, gendry)
}
println("arya = $arya")
println("gendry = $gendry")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.