6
votes

Récupérer l'avenir sous-jacent dans la gauche des chats?

Si j'ai un Future [Soit [String, Int]] qui représente soit un message d'erreur possible ( String ), soit un calcul réussi ( Int ), il est simple de déplacer l'échec potentiel de Future vers le côté gauche comme message d'erreur:

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

Je m'attendrais à ce que quelque chose de similaire existe pour EitherT , mais peut-être que je n'arrive pas à savoir comment cela s'appelle. C'est relativement simple, mais implique de déballer et de re-boxer le EitherT, ce qui semble dérisoire:

def handleFailure(fe: Future[Either[String,Int]]) =
  f.recover({ case e: Exception => Left(s"failed because ${e.getMessage}"))

Les chats ont ajouté une instance de MonadError a il y a déjà , mais c'est spécifiquement pour récupérer directement dans le droit de l'un ou l'autre, pas pour remplacer le Either lui-même.

handleFailureT l'a-t-il implémenté Cats, et si oui comment s'appelle-t-il?


0 commentaires

3 Réponses :


1
votes

Après avoir passé plusieurs heures là-dessus, je suis assez certain qu'à partir de mars 2019, cette fonction n'est pas implémentée directement chez les chats. Cependant, la monade catsDataMonadErrorFForEitherT déjà existante permet de l'implémenter de manière la plupart du temps simple.

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

Je ne sais pas ce que le les implications de performance de la construction de la monade dans la classe implicite générique sont, mais cela fonctionne. Si vous n'avez pas besoin du cas générique, vous pouvez remplacer [A, B] par des types explicites.

Pendant que j'y étais, j'ai également écrit recoverWithFlat , handleErrorLeft et handleErrorWithFlat et le tout emballé dans un fichier EitherTUtils.scala

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


0 commentaires

1
votes

Voici une version généralisée de vos 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
  }

}

Je suis d'accord que les chats pourraient faciliter le travail avec les EitherTs "échoués", j'espère que nous voyons quelque chose comme ceci dans les versions futures.


0 commentaires

3
votes

Ce n'est pas du tout évident, mais je pense que c'est à cela que servent tentative et tryT . Par exemple:

val myTry: Try[Int] = Try(2)
val myFuture: Future[String] = Future.failed(new Exception())
val myTryET: EitherT[Try, Throwable, Int] = myTry.attemptT
val myFutureET: EitherT[Future, Throwable, String] = myFuture.attemptT

// alternatively
val myFutureET: EitherT[Future, Throwable, String] = EitherT(myFuture.attempt)

Il y avait un PR pour ajouter ceci à la documentation: https://github.com/typelevel/cats/pull/3178 - mais il n'apparaît pas dans la documentation actuellement. Cependant, vous pouvez le voir ici: https://github.com/typelevel/cats/blob/master/docs/src/main/tut/datatypes/eithert.md#from-applicativeerrorf-e-to- eithertf-ea


0 commentaires