Как мне обернуть исключение EitherT в левое?
Представь у меня 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
, Что я должен делать?
2 ответа
Я написал вспомогательный класс для этого.
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))
}
Дайте мне знать, если есть более элегантный более короткий путь.
Я бы следовал за типами.
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
thenFlatMapF
не будет производить OptionT[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
или дисперсия должна быть сделана, чтобы отобразить Throwable
в FailureMsg
, Одна из причин заключается в том, что IO
имеет ошибку по умолчанию, выраженную как Throwable
, Нельзя просто смешивать FailureMsg
а также Throwable
, Любое наследование требуется, чтобы FailureMsg
имеет тип 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