Восстановление лежащего в основе будущего в кошачьих левых? - PullRequest
0 голосов
/ 28 февраля 2019

Если у меня есть 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, который кажется грязным:

def handleFailureT(fe: EitherT[Future, String, Int]) =
  EitherT(handleFailure(et.value)) // See above for handleFailure definition

Кошки действительно добавили MonadError экземпляр в то время как , но это специально для восстановленияпрямо в Either's right, а не для замены самого Either.

Является ли handleFailureT реализованным в нем Cats, и если да, то как он называется?

Ответы [ 2 ]

0 голосов
/ 16 июля 2019

Вот обобщенная версия вашего 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, надеюсь, мы увидим что-то подобное в будущих версиях.

0 голосов
/ 01 марта 2019

Потратив на это несколько часов, я вполне уверен, что по состоянию на март 2019 года эта функция непосредственно не реализована у кошек.Тем не менее, уже существующая catsDataMonadErrorFForEitherT монада действительно позволяет реализовать ее в основном несложным образом.

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

Я не уверен, каковы последствия для производительности построения монады в рамках универсальный неявный класс есть, но он работает.Если вам не нужен общий случай, вы можете заменить [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))
      }
  }
}

Я подумал, что это может быть моим первым вкладом в кошек, но после нескольких часов навигации по макету библиотеки я понял, что изменения не будут тривиальными, и я неу меня еще нет уровня знаний, чтобы представлять их таким образом, чтобы не требовал значительной работы от других участников проекта.

Я могу попробовать еще раз, когда лучше пойму структуру библиотеки кошек.

...