0

想象一下我有OptionT[IO, Value]这样的

case class FailureMsg(code: String, ex: Option[Throwable])

val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
  .map { value ⇒
    println("Mapping over")
    value
  }
  .flatMapF[Int](_ ⇒ IO.raiseError(new RuntimeException("err1")))
  .toRight(FailureMsg("Code0", None))
  .recoverWith {
    case ex ⇒ // Not Throwable!
      EitherT.leftT[IO, Int](FailureMsg("Code1", Some(ex)))
  }
  .value

我怎样才能抓住err1它并将其包装成Left[FailureMsg]. 我希望recoverWith对我有所帮助,但令人惊讶的是它是mapLeft. 我应该怎么办 ?

4

2 回答 2

0

我写了帮助类来做到这一点。

implicit class EitherTExt[F[_], A, B](val obj: EitherT[F, A, B]) {
    def recoverThrowable(pf: PartialFunction[Throwable, Either[A, B]])(implicit A: ApplicativeError[F, Throwable]): EitherT[F, A, B] =
      EitherT(obj.value.recover(pf))
  }

让我知道是否有更优雅的更短方式。

于 2018-09-24T13:20:49.333 回答
0

我会按照类型。

val start: OptionT[IO, Int] = OptionT.some[IO](12345)

val thenMap: OptionT[IO, Int] = start.map { value ⇒
  println("Mapping over")
  value
}

// here it will get off the rails
val thenFlatMapF: OptionT[IO, Int] =
  thenMap.flatMapF[Int](_ ⇒ IO.raiseError(new RuntimeException("err1")))

val thenToRight: EitherT[IO, FailureMsg, Int] =
  thenFlatMapF.toRight(FailureMsg("Code0", None))

val result: IO[Either[FailureMsg, Int]] = thenToRight.value

thenFlatMapFOptionT[IO, Int]如果是这种情况,则不会产生IO.raiseError,因为没有默认映射Throwable到什么?并且您将在折叠结果中遇到异常IO.raiseError

第一次尝试修复它,将说明它:

val thenFlatMapF: OptionT[IO, Int] = thenMap.flatMapF[Int](_ ⇒ {
  IO.raiseError[Option[Int]](new RuntimeException("err1")).recoverWith {
    case err =>
      val result: Option[Int] = ???
      IO.pure(result)
  }
})

如何在不中断IO和返回的情况下就地处理错误Option以便OptionT[IO, Int]产生?

所以基本上,在这种情况下,如果您预计flatMapF会失败并需要有关错误的信息,那么最好将EitherT其作为容器,而不是OptionT.

完成后,可能的解决方案表明,leftMap应该在某个时间点或方差将 映射ThrowableFailureMsg. 原因之一是因为IO默认错误表示为Throwable。不能只是混合FailureMsgThrowable。要么继承是必需的,所以它FailureMsg是 type Throwable/Exception,或者应该在合适的地方对错误进行映射。

我的粗略解决方案是:

val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
  // ok, let's do some input interpretation
  .map { value ⇒
    println("Mapping over")
    value
  }
  // if no input, means error
  .toRight(FailureMsg("Code0", None))
  // if there is interpreted input to process, do it and map the errors
  .flatMapF(_ ⇒ IO.raiseError[Int](new RuntimeException("err1")).attempt.map(_.leftMap {
    case err: RuntimeException if err.getMessage == "err1" => FailureMsg("err1", Some(err))
    // just for illustration, that if you have temptation to map on error,
    // most likely there won't be only exception
    case t: Throwable => FailureMsg("unexpected", Some(t))
  }))
  .value

但是,通常的内容flatMapF将是一个单独的函数或效果,其中包括错误处理。像这样的东西:

val doJob: Int => IO[Either[FailureMsg, Int]] = arg =>
  IO.raiseError[Int](new RuntimeException("err1")).attempt.map(_.leftMap {
    case err: RuntimeException if err.getMessage == "err1" => FailureMsg("err1", Some(err))
    case t: Throwable => FailureMsg("unexpected", Some(t))
  })

val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
  .map { value ⇒
    println("Mapping over")
    value
  }
  .toRight(FailureMsg("Code0", None))
  .flatMapF(arg ⇒ doJob(arg))
  .value
于 2018-10-03T15:13:35.493 回答