【问题标题】:Stacking M, Either and Writer堆叠 M、Either 和 Writer
【发布时间】:2018-07-22 11:39:12
【问题描述】:

我目前正在使用 EitherT 堆叠 Futures 和 Eithers:

type ErrorOr[A] = Either[Error, A]

def getAge: Future[ErrorOr[Int]] = ???
def getDob(age: Int): ErrorOr[LocalDate] = ???

for {
  age <- EitherT(getAge)
  dob <- EitherT.fromEither[Future](getDob(age))
} yield dob

我现在想介绍一下 Writer monad,即

type MyWriter[A] = Writer[Vector[String], ErrorOr[A]]

def getAge: Future[MyWriter[Int]] = ???
def getDob(age: Int): MyWriter[LocalDate] = ???

我的问题是,对getAgegetDob 调用进行排序的最佳方法是什么?我知道单子可以堆叠,即Future -&gt; Writer -&gt; Either,但在这种情况下我可以继续使用EitherT吗?如果是这样怎么办?

【问题讨论】:

    标签: scala monads monad-transformers scala-cats


    【解决方案1】:

    是的,您可以像这样继续使用WriterT monad 转换器:

    type FutureErrorOr[A] = EitherT[Future, Error, A]
    type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
    

    如果你解压这个类型,它类似于Future[Either[Error, Writer[Vector[String], A]]

    现在棘手的部分是将你的函数提升到这个基础 monad 中,所以这里有一些例子:

    def getAge: FutureErrorOr[Int] = ???
    def getDob(age: Int): ErrorOr[LocalDate] = ???
    
    for {
      age <- WriterT.liftF(getAge)
      dob <- WriterT.liftF(EitherT.fromEither(getDob(age)))
    } yield dob
    

    为了使这更容易,您可以查看cats-mtl.

    【讨论】:

      【解决方案2】:

      这与@luka-jacobowitz 给出的方法略有不同。通过他的方法,在“失败”之前发生的任何日志都将丢失。鉴于建议的类型:

      type FutureErrorOr[A] = EitherT[Future, Error, A]
      type MyStack[A] = WriterT[FutureErrorOr, Vector[String], A]
      

      我们发现,如果我们使用WriterTrun 方法扩展MyStack[A] 的值,我们会得到以下类型的值:

      FutureErrorOr[(Vector[String], A)]
      

      这与以下内容相同:

      EitherT[Future, Error, (Vector[String], A)]
      

      然后我们可以使用valueEitherT 方法进一步扩展:

      Future[Either[Error, (Vector[String], A)]]
      

      在这里我们可以看到,检索包含结果日志的元组的唯一方法是程序是否“成功”(即右关联)。如果程序失败,则在程序运行时创建的任何以前的日志都无法访问。

      如果我们采用原始示例并稍微修改它以在每一步之后记录一些内容,并且我们假设第二步返回一个 Left[Error] 类型的值:

      val program = for {
        age <- WriterT.liftF(getAge)
        _ <- WriterT.tell(Vector("Got age!"))
        dob <- WriterT.liftF(EitherT.fromEither(getDob(age))) // getDob returns Left[Error]
        _ <- WriterT.tell(Vector("Got date of birth!"))
      } yield {
        dob
      }
      

      那么当我们评估结果时,我们只会返回包含错误的左侧 case,没有任何日志:

      val expanded = program.run.value // Future(Success(Left(Error)))
      val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // Left(Error), no logs!!
      

      为了获得运行我们的程序产生的值以及在程序失败之前生成的日志,我们可以像这样重新排序建议的 monad:

      type MyWriter[A] = WriterT[Future, Vector[String], A]
      type MyStack[A] = EitherT[MyWriter, Error, A]
      

      现在,如果我们使用EitherTvalue 方法扩展MyStack[A],我们会得到以下类型的值:

      WriterT[Future, Vector[String], Either[Error, A]]
      

      我们可以使用WriterTrun 方法进一步扩展它,为我们提供一个包含日志和结果值的元组:

      Future[(Vector[String], Either[Error, A])]
      

      使用这种方法,我们可以像这样重写程序:

      val program = for {
        age <- EitherT(WriterT.liftF(getAge.value))
        _ <- EitherT.liftF(WriterT.put(())(Vector("Got age!")))
        dob <- EitherT.fromEither(getDob(age))
        _ <- EitherT.liftF(WriterT.put(())(Vector("Got date of birth!")))
      } yield {
        dob
      }
      

      并且当我们运行它时,即使在程序执行过程中出现故障,我们也可以访问结果日志:

      val expanded = program.value.run // Future(Success((Vector("Got age!), Left(Error))))
      val result = Await.result(expanded, Duration.apply(2, TimeUnit.SECONDS)) // (Vector("Got age!), Left(Error))
      

      诚然,这个解决方案需要更多样板,但我们总是可以定义一些帮助器来帮助解决这个问题:

      implicit class EitherTOps[A](eitherT: FutureErrorOr[A]) {
        def lift: EitherT[MyWriter, Error, A] = {
          EitherT[MyWriter, Error, A](WriterT.liftF[Future, Vector[String], ErrorOr[A]](eitherT.value))
        }
      }
      
      implicit class EitherOps[A](either: ErrorOr[A]) {
        def lift: EitherT[MyWriter, Error, A] = {
          EitherT.fromEither[MyWriter](either)
        }
      }
      
      def log(msg: String): EitherT[MyWriter, Error, Unit] = {
        EitherT.liftF[MyWriter, Error, Unit](WriterT.put[Future, Vector[String], Unit](())(Vector(msg)))
      }
      
      val program = for {
        age <- getAge.lift
        _ <- log("Got age!")
        dob <- getDob(age).lift
        _ <- log("Got date of birth!")
      } yield {
        dob
      }
      

      【讨论】:

      • 不知道为什么这条评论没有被赞成。非常有用的见解,谢谢 Luis
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-18
      • 2023-03-18
      • 1970-01-01
      • 2017-10-10
      • 1970-01-01
      • 2014-03-31
      • 2016-05-03
      相关资源
      最近更新 更多