本文介绍了在 Scalaz 中自定义 Future、Either 和 Writer 的组合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我上一个问题的后续:测序Scalaz WriterT 和Either with for-yield

This is a follow up to my previous question: Sequencing both Scalaz WriterT and Either with for-yield

以下代码块是使用 EitherTEitherTFutureEitherWriter 进行排序的示例code>WriterT monad 转换器;下面的问题是关于如何巧妙地改变那堆转换器的行为.

The following code block is an example of sequencing Future, Either and Writer using the EitherT and WriterT monad transformers; the following question is about how to subtly change the behaviour of that stack of transformers.

import scalaz._, Scalaz._

class Example[F[_], L] (val logFn: (String) => L)(implicit val f: Monad[F], l: Monoid[L])
{
  type T = Throwable
  type EF[α] = EitherT[F, T, α]
  type WEF[α] = WriterT[EF, L, α]

  private def unreliableInt (i: Int): T Either Int = new java.util.Random ().nextBoolean match {
    case false => Right (i)
    case true => Left (new Exception (":-("))
  }

  private def fn (i: Int): WEF[Int] = WriterT.put[EF, L, Int](EitherT.fromEither[F, T, Int](f.point (unreliableInt (i))))(l.zero)

  private def log (msg: String): WEF[Unit] = WriterT.put[EF, L, Unit](EitherT.right[F, T, Unit](f.point (())))(logFn (msg))

  private def foo (): WEF[Int] = for {
    _ <- log ("Start")
    x <- fn (18)
    _ <- log ("Middle")
    y <- fn (42)
    _ <- log ("End")
  } yield x + y

  def bar (): F[(Option[Int], L)] = {
    val barWEF: WEF[Int] = foo ()

    // Pull out the logs.
    val logsEF: EF[L] = barWEF.written
    val logsF: F[L] = logsEF.toEither.map {
      case Right (x) => x
      case Left (e) => logFn(s"Not the logs we are looking for ${e.getMessage}")
    }

    // Pull out the value.
    val resEF: EF[Int] = barWEF.value
    val resF: F[Option[Int]] = resEF.run.map {
      case \/- (r) => r.some
      case -\/ (ex) => None
    }

    for {
      logs <- logsF
      response <- resF
    } yield (response, logs)
  }
}

object Program
{
  def main (args : Array[String]) = {
    import scala.concurrent._
    import scala.concurrent.duration._
    import ExecutionContext.Implicits.global

    type L = List[String]
    type F[α] = Future[α]

    implicit val l: Monoid[L] = new Monoid[L] { def zero = Nil; def append (f1: L, f2: => L) = f1 ::: f2 }
    implicit val f: Monad[F] = scalaz.std.scalaFuture.futureInstance

    def createLog (s: String) = s :: Nil
    val example = new Example[F, L] (createLog)
    val result = Await.result (example.bar (), 5 seconds)
    println ("Context logs attached:" + result._2.foldLeft ("") { (a, x) => a + "\n$ " + s"$x"})
    println ("Result:" + result._1)
  }
}

函数 foo 没有按照我的需要运行;函数 barmain 函数说明了这个问题.

The function foo does not behave as I need it to; the function bar and the main function illustrate the problem.

所需的行为是 main 将始终打印以下结果之一:

The desired behaviour is such that main will always print one of the following results:

Context logs attached:
$ Start
Result:None

Context logs attached:
$ Start
$ Middle
Result:None

Context logs attached:
$ Start
$ Middle
$ End
Result:Some(60)

main 函数不应该打印以下内容:

The main function should, however, never print the following:

Context logs attached:
$ Not the logs we are looking for :-(
Result:None

但这正是它的作用.当 fn1fn2 都成功时,foo 会按照要求运行并且 main 打印出所有日志.如果 fn1fn2 中的一个或两个返回 Left,则函数 bar 不返回日志并且 main 继续打印只有例外.没有办法看到它在日志中走了多远.

But that is exactly what it does. When both fn1 and fn2 are successful, foo behaves as required and main prints out all of the logs. If either or both fn1 or fn2 return a Left the function bar returns no logs and main goes on to print only the exception. Theres no way to see how far it got in the logs.

似乎这个特定的转换器堆栈的行为方式是,如果序列中存在 -\/ ,则日志上下文会被简单地映射出来......

It seems that this particular stack of transformers behaves in such a way that if ever there is a -\/ in the sequence, the logging context is simply mapped out...

查看 WriterT 的 Scalaz 代码,情况可能是这样:

Looking at the Scalaz code for WriterT this looks likely to be the case:

final case class WriterT[F[_], W, A](run: F[(W, A)])

WriterT 是一个 case 类,它的唯一成员是 run.对于这个例子,run 是我们的日志上下文 (A) 和我们的结果的元组,两者都包裹在同一个 EitherT (EitherT>F).WA 在数据中由一种类型绑定,因此它们要么大部分都在 Left 内,要么都在 Right 内.

WriterT is a case class whose only member is run. With respect to this example run is a tuple of our logging context (A) and our result, both wrapped in the same EitherT (F). W and A are bound in data by a type so either they most both be inside a Left or both be inside a Right.

我可以推测我需要一个定制版本的 WriterT ,它的行为略有不同,它的数据有点像这样存储,只允许在一个新的 Applicative[F].点:

I can speculate that I need a customised version of WriterT that behaves slightly differently, storing its data a little like this, allowing access to the writer part only inside a fresh Applicative[F].point:

final case class WriterT[F[_], W, A](wF: F[W], vF:F[A]) {
  def run: F[(W, A)] = for {
    w <- wF
    v <- vF
  } yield (w, v)
}

虽然我不确定创建我自己的 WriterT 类型类是否是解决这个问题和实现我想要的行为的可取方法.

Though I'm not really sure if creating my own WriterT type class would be the advisable approach to solving this problem and achieving my desired behaviour.

我有哪些选择?

推荐答案

本文:组合一元效应 解释了这个问题.

This article: Composing monadic effects explains the issue.

所以...

type MyMonad e w a = ErrorT e (Writer w) a 同构于 (Either e a, w)

type MyMonad e w a = WriterT w (Either e) a 同构于 Either r (a, w)

按如下方式重新排列 monad 转换器堆栈可以解决问题:

Reordering the stack of monad transformers as follows solves the problem:

import scalaz._, Scalaz._

class Example[F[_], L] (val logFn: (String) => L)(implicit val f: Monad[F], l: Monoid[L])
{
  type T = Throwable
  type WF[α] = WriterT[F, L, α]
  type EWF[α] = EitherT[WF, T, α]

  private def unreliableInt (i: Int): T Either Int = {
    new java.util.Random ().nextBoolean match {
      case false => Right (i)
      case true => Left (new Exception (":-("))
    }
  }

  private def fn (i: Int): EWF[Int] = unreliableInt (i) match {
    case Left (left) => EitherT.left [WF, T, Int] (WriterT.put[F, L, T] (f.point (left))(l.zero))
    case Right (right) => EitherT.right [WF, T, Int] (WriterT.put[F, L, Int] (f.point (right))(l.zero))
  }

  private def log (msg: String): EWF[Unit] = { EitherT.right[WF, T, Unit](WriterT.put[F, L, Unit] (f.point (()))(logFn (msg))) }

  private def foo (): EWF[Int] = for {
    a <- log ("Start")
    x <- fn (18)
    b <- log ("Middle")
    y <- fn (42)
    c <- log ("End")
  } yield x + y

  def bar (): F[(Option[Int], L)] = {
    val barEWF: EWF[Int] = foo ()

    // Pull out the logs.
    val logsF: F[L] = barEWF.run.written

    // Pull out the value.
    val resF: F[Option[Int]] = barEWF.run.value.map {
      case \/- (r) => r.some
      case -\/ (ex) => None
    }

    for {
      logs <- logsF
      response <- resF
    } yield (response, logs)
  }
}

object Program
{
  def main (args : Array[String]) = {
    import scala.concurrent._
    import scala.concurrent.duration._
    import ExecutionContext.Implicits.global

    type L = List[String]
    type F[α] = Future[α]

    implicit val l: Monoid[L] = new Monoid[L] { def zero = Nil; def append (f1: L, f2: => L) = f1 ::: f2 }
    implicit val f: Monad[F] = scalaz.std.scalaFuture.futureInstance

    def createLog (s: String) = s :: Nil
    val example = new Example[F, L] (createLog)
    val result = Await.result (example.bar (), 5 seconds)
    println ("Context logs attached:" + result._2.foldLeft ("") { (a, x) => a + "\n$ " + s"$x"})
    println ("Result:" + result._1)
  }
}

这篇关于在 Scalaz 中自定义 Future、Either 和 Writer 的组合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-12 13:41