Как мне обернуть исключение EitherT в левое? - PullRequest
0 голосов
/ 24 сентября 2018

Представьте, что у меня есть 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 ]

0 голосов
/ 03 октября 2018

Я бы следовал типам.

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
0 голосов
/ 24 сентября 2018

Я написал вспомогательный класс для этого.

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))
  }

Дайте мне знать, если есть более элегантный более короткий путь.

...