Either<E, A>
Ior<E, A>
Raise<E>
object UserNotFound
data class User(val id: Long)
val user: Either<UserNotFound, User> = User(1).right()
fun Raise<UserNotFound>.user(): User = User(1)
val error: Either<UserNotFound, User> = UserNotFound.left()
fun Raise<UserNotFound>.error(): User = raise(UserNotFound)
val res = either { user() }
fun Raise<UserNotFound>.res(): User = user.bind()
data class UserNotFound(val message: String)
fun User.isValid(): Either<UserNotFound, Unit> = either {
ensure(id > 0) {
UserNotFound("User without a valid id: $id") }
}
fun Raise<UserNotFound>.isValid(user: User): User {
ensure(user.id > 0) {
UserNotFound("User without a valid id: ${user.id}") }
return user
}
fun example() {
when (res) {
is Left -> fail("No logical failure occurred!")
is Right -> res.value shouldBe User(1)
}
fold(
block = { res() },
recover = { _: UserNotFound ->
fail("No logical failure occurred!") },
transform = { i: User -> i shouldBe User(1) }
)
}
data class UserAlreadyExists(
val username: String, val email: String)
suspend fun Raise<UserAlreadyExists>.insertUser(
username: String, email: String): Long =
catch({
UsersQueries.insert(username, email)
}) { e: SQLException ->
if (e.isUniqueViolation())
raise(UserAlreadyExists(username, email))
else throw e
}
suspend fun insertUser(
username: String, email: String
): Either<UserAlreadyExists, Long> =
Either.catchOrThrow<SQLException, Long> {
UsersQueries.insert(username, email)
}.mapLeft { e ->
if (e.isUniqueViolation()) UserAlreadyExists(username, email)
else throw e
}
data class NotEven(val i: Int)
fun Raise<NotEven>.isEven(i: Int): Int =
i.also { ensure(i % 2 == 0) { NotEven(i) } }
fun isEven2(i: Int): Either<NotEven, Int> =
either { isEven(i) }
val errors = nonEmptyListOf(
NotEven(1), NotEven(3), NotEven(5),
NotEven(7), NotEven(9)).left()
fun example() {
(1..10).mapOrAccumulate { isEven(it) } shouldBe errors
(1..10).mapOrAccumulate { isEven2(it).bind() } shouldBe errors
}
data class User(val name: String, val age: Int)
sealed interface UserProblem {
object EmptyName: UserProblem
data class NegativeAge(val age: Int): UserProblem
}
data class User private constructor(
val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int)
: Either<UserProblem, User> = either {
ensure(name.isNotEmpty()) {
UserProblem.EmptyName }
ensure(age >= 0) {
UserProblem.NegativeAge(age) }
User(name, age)
}
}
}
data class User private constructor(
val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int):
Either<NonEmptyList<UserProblem>, User> = either {
zipOrAccumulate(
{ ensure(name.isNotEmpty()) {
UserProblem.EmptyName } },
{ ensure(age >= 0) {
UserProblem.NegativeAge(age) } }
) { _, _ -> User(name, age) }
}
}
}