是否有任何理由使用 suspend fun fn(...): Either<Throwable, A> 而不是 suspend fun fn(...): A?
Is there any reason to use suspend fun fn(...): Either<Throwable, A> instead of suspend fun fn(...): A?
我正在考虑有关 suspend
的内容,Arrow 的文档对此进行了详细解释:suspend () -> A
提供与 IO<A>
相同的保证。
因此,根据文档,仅使用 suspend
我们将不纯函数转换为纯函数:
不纯
fun log(message: String): Unit = println(message)
fun main(): Unit {
log("Hey!")
}
纯
suspend fun log(message: String): Unit = println(message)
fun main(): Unit = runBlocking {
log("Hey!")
}
仅添加 suspend
即可将函数变为纯函数这一事实令人惊讶,但 clearly explained in the doc。
考虑到这一点,我的下一个疑问与可能导致错误 (Throwable
) 或值 A
.
的业务服务建模有关
到目前为止,我一直在做这样的事情:
suspend fun log(message: String): Either<Throwable, Unit> = either { println(message) }
suspend fun add(sum1: Int, sum2: Int): Either<Throwable, Int> = either { sum1 + sum2 }
suspend fun main() {
val program = either<Throwable, Unit> {
val sum = add(1, 2).bind()
log("Result $sum").bind()
}
when(program) {
is Either.Left -> throw program.value
is Either.Right -> println("End")
}
}
但是,假设 suspend fun fn() : A
是纯的并且等价于 IO<A>
,我们可以将上面的程序重写为:
suspend fun add(sum1: Int, sum2: Int): Int = sum1 + sum2
suspend fun log(message: String): Unit = println(message)
fun main() = runBlocking {
try {
val sum = add(1, 2)
log("Result $sum")
} catch( ex: Throwable) {
throw ex
}
}
有什么理由更喜欢 suspend fun fn(...): Either<Throwable, A>
而不是暂停 fun fn(...): A
?
如果您想使用 Throwable
,有 2 个选项,kotlin.Result
或 arrow.core.Either
。
最大的区别在runCatching
和Either.catch
之间。其中runCatching
会捕获所有异常,而Either.catch
只会捕获non-fatal exceptions。所以 Either.catch
会防止你误吞 kotlin.coroutines.CancellationException
.
您应该将上面的代码更改为以下代码,因为 either { }
不会捕获任何异常。
suspend fun log(message: String): Either<Throwable, Unit> =
Either.catch { println(message) }
suspend fun add(sum1: Int, sum2: Int): Either<Throwable, Int> =
Either.catch { sum1 + sum2 }
Is there any reason to prefer suspend fun fn(...): Either<Throwable, A> over suspend fun fn(...): A?
是的,在 return 类型中使用 Result
或 Either
的原因是强制调用者解决错误。强制用户解决错误,即使在 IO<A>
或 suspend
内仍然很有价值,因为最后的 try/catch
是可选的。
但是使用 Either
对跟踪整个业务领域的错误变得真正有意义。或者以类型化的方式跨层解决它们。
例如:
data class User(...)
data class UserNotFound(val cause: Throwable?)
fun fetchUser(): Either<UserNotFound, User> = either {
val user = Either.catch { queryOrNull("SELECT ...") }
.mapLeft { UserNotFound(it) }
.bind()
ensureNotNull(user) { UserNotFound() }
}
我正在考虑有关 suspend
的内容,Arrow 的文档对此进行了详细解释:suspend () -> A
提供与 IO<A>
相同的保证。
因此,根据文档,仅使用 suspend
我们将不纯函数转换为纯函数:
不纯
fun log(message: String): Unit = println(message)
fun main(): Unit {
log("Hey!")
}
纯
suspend fun log(message: String): Unit = println(message)
fun main(): Unit = runBlocking {
log("Hey!")
}
仅添加 suspend
即可将函数变为纯函数这一事实令人惊讶,但 clearly explained in the doc。
考虑到这一点,我的下一个疑问与可能导致错误 (Throwable
) 或值 A
.
到目前为止,我一直在做这样的事情:
suspend fun log(message: String): Either<Throwable, Unit> = either { println(message) }
suspend fun add(sum1: Int, sum2: Int): Either<Throwable, Int> = either { sum1 + sum2 }
suspend fun main() {
val program = either<Throwable, Unit> {
val sum = add(1, 2).bind()
log("Result $sum").bind()
}
when(program) {
is Either.Left -> throw program.value
is Either.Right -> println("End")
}
}
但是,假设 suspend fun fn() : A
是纯的并且等价于 IO<A>
,我们可以将上面的程序重写为:
suspend fun add(sum1: Int, sum2: Int): Int = sum1 + sum2
suspend fun log(message: String): Unit = println(message)
fun main() = runBlocking {
try {
val sum = add(1, 2)
log("Result $sum")
} catch( ex: Throwable) {
throw ex
}
}
有什么理由更喜欢 suspend fun fn(...): Either<Throwable, A>
而不是暂停 fun fn(...): A
?
如果您想使用 Throwable
,有 2 个选项,kotlin.Result
或 arrow.core.Either
。
最大的区别在runCatching
和Either.catch
之间。其中runCatching
会捕获所有异常,而Either.catch
只会捕获non-fatal exceptions。所以 Either.catch
会防止你误吞 kotlin.coroutines.CancellationException
.
您应该将上面的代码更改为以下代码,因为 either { }
不会捕获任何异常。
suspend fun log(message: String): Either<Throwable, Unit> =
Either.catch { println(message) }
suspend fun add(sum1: Int, sum2: Int): Either<Throwable, Int> =
Either.catch { sum1 + sum2 }
Is there any reason to prefer suspend fun fn(...): Either<Throwable, A> over suspend fun fn(...): A?
是的,在 return 类型中使用 Result
或 Either
的原因是强制调用者解决错误。强制用户解决错误,即使在 IO<A>
或 suspend
内仍然很有价值,因为最后的 try/catch
是可选的。
但是使用 Either
对跟踪整个业务领域的错误变得真正有意义。或者以类型化的方式跨层解决它们。
例如:
data class User(...)
data class UserNotFound(val cause: Throwable?)
fun fetchUser(): Either<UserNotFound, User> = either {
val user = Either.catch { queryOrNull("SELECT ...") }
.mapLeft { UserNotFound(it) }
.bind()
ensureNotNull(user) { UserNotFound() }
}