Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: MonadError.transformWith #2161

Closed
alexandru opened this issue Jan 25, 2018 · 3 comments · Fixed by #3146
Closed

Proposal: MonadError.transformWith #2161

alexandru opened this issue Jan 25, 2018 · 3 comments · Fixed by #3146

Comments

@alexandru
Copy link
Member

alexandru commented Jan 25, 2018

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:

  1. Task.transformWith
  2. Future.transformWith

Note — I'd prefer the version with the two function parameters, because I'd rather avoid the extra Either boxing.

Implementation notes:

  1. cats.effect.IO is already optimized for this operation
  2. monix.eval.Task is already optimized for this operation

Basically I'd like to not feel bad about adding error handling.

@joan38
Copy link
Contributor

joan38 commented Dec 30, 2018

Isn't this solved by redeem?

@opensorceror
Copy link

opensorceror commented Jul 4, 2019

@joan38 It doesn't look like redeem is not available on MonadError. (It is available, however, on the cats.effect.IO type. I'm using cats-effect 1.2.0.)

@joan38
Copy link
Contributor

joan38 commented Jul 6, 2019

@opensorceror see #2237 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants