-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
So MonadError has attempt and handleErrorWith and for many use-cases one has to introduce both map / flatMap transformations and handleErrorWith handlers in the same "frame".
E.g. what happens for data types implementing cats-effect like cats.effect.IO and monix.eval.Task is something like this
fa.attempt.flatMap {
case Right(next) => continueLoop(next)
case Left(error) => stopLoop(error)
}This is more or less the equivalent of this pattern:
var streamError = true
try {
val next = fa()
streamError = false
continueLoop(next)
} catch {
case error if streamError =>
stopLoop(error)
}Well the problem is that usage of either attempt or handleErrorWith involves 2 flatMap (bind) frames instead of one. And as we all know, the performance of IO / Task data types, compared with plain function calls, is terrible.
The proposal is to introduce an alternative to attempt.flatMap like so:
def transformWith[A, B](fa: F[A])(bind: A => F[B], recover: Throwable => F[B]): F[B]Just did.a fast benchmark to show what the difference can be:
attempt 10000 thrpt 10 2383.878 ± 46.546 ops/s
onErrorHandleWith 10000 thrpt 10 2813.948 ± 176.036 ops/s
transformWith 10000 thrpt 10 4115.213 ± 40.821 ops/s
Precedents:
Note — I'd prefer the version with the two function parameters, because I'd rather avoid the extra Either boxing.
Implementation notes:
cats.effect.IOis already optimized for this operationmonix.eval.Taskis already optimized for this operation
Basically I'd like to not feel bad about adding error handling.