Я бы следовал типам.
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