Created
January 15, 2021 11:53
-
-
Save raulraja/ac7a489c01193bcf5e8fdc59c89b5156 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
package dbactions | |
import arrow.continuations.Effect | |
import arrow.continuations.generic.DelimitedScope | |
import kotlin.reflect.KClass | |
/** | |
* An abstract connection | |
*/ | |
interface Connection { | |
suspend fun <A : Entity> load(klass: KClass<A>, id: Id): A? | |
suspend fun <A : Entity> save(entity: A): A | |
} | |
/** | |
* Local connection with in memory map | |
*/ | |
val fooId = Id("foo") | |
object LocalConnection : Connection { | |
private val localDb: MutableMap<Pair<KClass<*>, Id>, Any> = | |
mutableMapOf( | |
Foo::class to fooId to Foo(fooId) | |
) | |
override suspend fun <A : Entity> load(klass: KClass<A>, id: Id): A? = | |
localDb[klass to id] as? A? | |
override suspend fun <A : Entity> save(entity: A): A { | |
localDb[entity::class to entity.id] = entity | |
return entity | |
} | |
} | |
/** | |
* An ADT for DB ops | |
*/ | |
sealed class DBAction<out A> { | |
companion object { | |
fun <A> pure(value: A): DBAction<A> = Pure(value) | |
} | |
} | |
data class Load<A : Entity>(val klass: KClass<A>, val id: Id) : DBAction<A>() | |
data class Save<out A : Entity>(val entity: A) : DBAction<A>() | |
data class Pure<out A>(val value: A) : DBAction<A>() | |
data class Raise(val ex: String) : DBAction<Nothing>() | |
/** | |
* All entities have an Id | |
*/ | |
interface Entity { | |
val id: Id | |
} | |
inline class Id(val value: String) | |
/** | |
* Mock model | |
*/ | |
data class Foo(override val id: Id, var foo: String? = null) : Entity | |
/** | |
* A DB effect provides syntax for monadic or other ops | |
* and has access to shifting with control() out of the block with a | |
* value of DBAction<A> | |
*/ | |
interface DB<A> : Effect<DBAction<A>> { | |
/** | |
* Connection is abstract | |
*/ | |
val connection: Connection | |
/** | |
* load the right id or short-circuit with a pure value | |
*/ | |
suspend operator fun <B : Entity> Load<B>.invoke(): B = | |
connection.load(klass, id) | |
?: control().shift(Raise("id not found for ${klass to id}")) | |
suspend operator fun <B : Entity> Save<B>.invoke(): B = | |
connection.save(entity) | |
suspend operator fun <B> Pure<B>.invoke(): B = | |
value | |
suspend operator fun Raise.invoke(): Nothing = | |
control().shift(Raise(ex)) | |
companion object { | |
operator fun <A> invoke(control: DelimitedScope<DBAction<A>>, connection: Connection): DB<A> = | |
object : DB<A> { | |
override fun control(): DelimitedScope<DBAction<A>> = control | |
override val connection: Connection = connection | |
} | |
} | |
} | |
/** | |
* Syntax for LocalConnection.db { ... } or RemoteConnection.db { .. } | |
*/ | |
suspend fun <A> Connection.db(f: suspend DB<*>.() -> A): DBAction<A> = | |
Effect.suspended({ DB(it, this) }, DBAction.Companion::pure, f) | |
/** | |
* Syntax for old school db { ... }(LocalConnection) or db { .. }(RemoteConnection) | |
*/ | |
suspend fun <A> db(f: suspend DB<*>.() -> A): suspend (Connection) -> DBAction<A> = | |
{ it.db(f) } | |
suspend fun main() { | |
val action = db { | |
val entity = Load(Foo::class, fooId)() | |
entity.foo = "bah" | |
Save(entity)() | |
//Raise("This would short-circuit and IDE knows about it")() | |
val otherEntity = Load(Foo::class, fooId)() | |
otherEntity | |
} | |
val localExecuted = action(LocalConnection) | |
println(localExecuted) | |
//Pure(value=Foo(id=Id(value=foo), foo=bah)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment