Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Either-Syntax for SqlDelight transactions
import arrow.continuations.Effect
import arrow.continuations.generic.DelimitedScope
import arrow.core.Either
import arrow.core.right
import com.squareup.sqldelight.TransactionWithReturn
import kotlin.coroutines.RestrictsSuspension
/**
* Either-Syntax for SqlDelight transactions.
*
* Upon encountering a [Either.Left] value it requests a rollback and returns the encountered [Either.Left] value.
* Otherwise it returns the returned value wrapped in [Either.Right].
*
* ```kotlin
* object Error
*
* fun operations(): List<Either<Error, Int>> =
* listOf(Either.Right("test-1"), Either.Left(Error), Either.Right("test-3"))
*
* fun saveOperations(db: Database, operations: List<Either<Error, String>>): Either<Error, Int> =
* db.transactionEither {
* operations.map { throwableOrString ->
* val string = throwableOrString.bind()
* db.table.insertString(string)
* db.table.selectIdForString(string)
* }
* }
* ```
*
* The above snippet will first insert `test-1` into `table` since `bind` can unwrap it from `Either.Right`,
* when it encounters `Left(Error)` it will rollback the transaction, thus the previous insert, and return `Left(Error)` from `transactionEither`.
*
* If `operations` was `listOf(Either.Right("test-1"), Either.Right("test-2"), Either.Right("test-3"))`
* it would insert all 3 values into the `table` and select their ids return `Right(listOf(1, 2, 3))`.
*/
fun <E, A> Database.transactionEither(f: suspend RestrictedEitherEffect<E, *>.() -> A): Either<E, A> =
transactionWithResult {
when (val res =
Effect.restricted<RestrictedEitherEffect<E, A>, Either<E, A>, A>(eff = { RestrictedEitherEffect(this, it) },
f = f,
just = { it.right() })) {
is Either.Left -> rollback(res)
is Either.Right -> res
}
}
@RestrictsSuspension
class RestrictedEitherEffect<E, A>(
db: TransactionWithReturn<Either<E, A>>,
private val scope: DelimitedScope<Either<E, A>>
) : Effect<Either<E, A>>, TransactionWithReturn<Either<E, A>> by db {
override fun control(): DelimitedScope<Either<E, A>> = scope
suspend fun <B> Either<E, B>.bind(): B =
when (this) {
is Either.Right -> value
is Either.Left -> control().shift(this@bind)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment