如果我有一个表示可能的错误消息(Future[Either[String, Int]]
)或成功计算(String
)的Int
,将Future
的潜在故障移动到左侧很简单,作为错误消息:
def handleFailure(fe: Future[Either[String,Int]]) =
f.recover({ case e: Exception => Left(s"failed because ${e.getMessage}"))
我希望EitherT
存在类似的东西,但也许我不知道它叫什么。这是相对简单的,但涉及拆箱和重新装箱EitherT感觉kludgey:
def handleFailureT(fe: EitherT[Future, String, Int]) =
EitherT(handleFailure(et.value)) // See above for handleFailure definition
Cats确实添加了MonadError
实例while ago,但它专门用于直接恢复到Either的right
,而不是替换Either本身。
handleFailureT
是否实施了它Cats,如果是这样,它叫什么?
花了几个小时后,我相当确定,截至2019年3月,这个功能没有直接在猫中实现。但是,已经存在的catsDataMonadErrorFForEitherT
monad确实可以以一种基本上不复杂的方式实现它。
implicit class EitherTFutureAdditions[A, B](et: EitherT[Future, A, B]) {
val me = EitherT.catsDataMonadErrorFForEitherT[Future, Throwable, A]
def recoverLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.recoverWith[B](et) { case t: Throwable =>
EitherT.fromEither[Future](Left(pf(t)))
}
}
我不确定在泛型隐式类中构造monad的性能影响是什么,但是它有效。如果您不需要通用案例,则可能需要将[A, B]
替换为显式类型。
当我在它的时候,我还写了recoverWithFlat
,handleErrorLeft
和handleErrorWithFlat
并将它们全部打包成文件EitherTUtils.scala
// Place this in a new file and then use it like so:
//
// import EitherTUtils.EitherTFutureAdditions
//
// val et: EitherT[Future, String, Int] =
// EitherT(Future.failed[Either[String, Int]](new Exception("example")))
// et recoverLeft {
// case e: Exception => s"Failed with reason ${e.getMessage}"
// }
//
object EitherTUtils {
/**
* Convenience additions for recovering and handling Future.failed within an EitherT
*
* @see [[cats.ApplicativeError]] for recover, recoverWith, handleError, handleErrorWith, and attemptT
*
* @param et a Futured EitherT
* @tparam A the Either's left type
* @tparam B the Either's right type
*/
implicit class EitherTFutureAdditions[A, B](et: EitherT[Future, A, B]) {
val me = EitherT.catsDataMonadErrorFForEitherT[Future, Throwable, A]
/**
* Recover from certain errors from this EitherT's Future (if failed) by mapping them to the EitherT's
* left value.
*
* @see [[recoverWithFlat]] for mapping to an Either[Future, A, B]
*
* @see [[handleErrorWithFlat]] to handle any/all errors.
*/
def recoverLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.recoverWith[B](et) {
case t: Throwable =>
EitherT.fromEither[Future](Left(pf(t)))
}
/**
* Recover from certain errors from this EitherT's Future (if failed) by mapping them to the EitherT's
* value.
*
* @see [[recoverLeft]] for mapping to an EitherT's left value.
*
* @see [[handleErrorWithFlat]] to handle any/all errors.
*/
def recoverWithFlat(pf: PartialFunction[Throwable, Either[A, B]]): EitherT[Future, A, B] =
me.recoverWith[B](et) {
case t: Throwable =>
EitherT.fromEither[Future](pf(t))
}
/**
* Handle any error from this EitherT's Future (if failed) by mapping them to the EitherT's left value.
*
* @see [[recoverWithFlat]] for handling only certain errors
*
* @see [[handleErrorLeft]] for mapping to the EitherT's left value
*/
def handleErrorLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.handleErrorWith[B](et) { t =>
EitherT.fromEither[Future](Left[A, B](pf(t)))
}
/**
* Handle any error from this EitherT's Future (if failed) by mapping them to the EitherT's value.
*
* @see [[recoverWithFlat]] for handling only certain errors
*
* @see [[handleErrorLeft]] for mapping to the EitherT's left value
*/
def handleErrorWithFlat(pf: PartialFunction[Throwable, Either[A, B]]): EitherT[Future, A, B] =
me.handleErrorWith[B](et) { t =>
EitherT.fromEither[Future](pf(t))
}
}
}
我认为这些可能是我对猫的第一次贡献,但经过几个小时的图书馆布局导航后,我意识到修改将是微不足道的,我还没有知识水平尚未以不需要的方式提交它们其他项目贡献者的重要工作。
一旦我更好地了解猫库结构,我可能会再试一次。
这是你的EitherTUtils
的通用版本:
import cats.data.EitherT
object EitherTUtils {
implicit class EitherTRecoverErrors[F[_], A, B, E](et: EitherT[F, A, B])(implicit me: MonadError[F, E]) {
type FE[X] = EitherT[F, A, X]
implicit val ME: MonadError[FE, E] = implicitly
def recoverLeft(pf: PartialFunction[E, A]): EitherT[F, A, B] =
ME.recoverWith(et)(pf.andThen(EitherT.leftT(_)))
def recoverWithFlat(pf: PartialFunction[E, Either[A, B]]): EitherT[F, A, B] =
ME.recoverWith(et)(pf.andThen(EitherT.fromEither(_)))
def handleErrorLeft(f: E => A): EitherT[F, A, B] =
ME.handleErrorWith(et)(f.andThen(EitherT.leftT(_)))
def handleErrorWithFlat(f: E => Either[A, B]): EitherT[F, A, B] =
ME.handleErrorWith(et)(f.andThen(EitherT.fromEither(_)))
}
}
object Usage {
import EitherTUtils._
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
val e: EitherT[Future, String, Int] = EitherT.liftF(Future.failed(new RuntimeException)).recoverLeft {
case e: IllegalStateException =>
e.getMessage
}
}
我同意猫可以更容易地使用“失败的”EitherTs,希望我们在未来的版本中看到类似的东西。