在Scalaz中定制Future,Either和Writer的组合

时间:2015-07-28 11:17:49

标签: scala scalaz monad-transformers

这是对我上一个问题的跟进:Sequencing both Scalaz WriterT and Either with for-yield

以下代码块是使用FutureEither monad变换器对WriterEitherTWriterT进行排序的示例;以下问题是关于如何巧妙地改变那堆变形金刚的行为。

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函数说明了问题。

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

Context logs attached:
$ Start
Result:None

Context logs attached:
$ Start
$ Middle
Result:None

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

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

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

但这正是它的作用。当fn1fn2都成功时,foo会按要求运行,main会打印出所有日志。如果fn1fn2中的任何一个或两者都返回Left,则函数bar将返回无日志,并且main继续仅打印异常。没有办法看到它在日志中有多远。

似乎这个特定的变换器堆栈的行为方式如果序列中有-\/,那么日志记录上下文就会被映射出来......

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

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

WriterT是一个案例类,其唯一成员是run。关于此示例,run是我们的日志记录上下文(A)的元组和我们的结果,两者都包含在相同的EitherTF)中。 WA按类型绑定在数据中,因此它们大多数都位于左侧,或者都位于右侧。

我可以推测我需要WriterT的自定义版本,其行为略有不同,将其数据存储得有点像这样,只允许在新的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类型类是否是解决此问题并实现我想要的行为的明智方法。

我有什么选择?

1 个答案:

答案 0 :(得分:5)

这篇文章:Composing monadic effects解释了这个问题。

因此...

type MyMonad e w a = ErrorT e (Writer w) a与(Either e a, w)

同构

type MyMonad e w a = WriterT w (Either e) aEither r (a, w)

同构

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

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