Skip to content

Instantly share code, notes, and snippets.

@raulraja
Created January 15, 2021 11:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raulraja/ac7a489c01193bcf5e8fdc59c89b5156 to your computer and use it in GitHub Desktop.
Save raulraja/ac7a489c01193bcf5e8fdc59c89b5156 to your computer and use it in GitHub Desktop.
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