问题描述
这是我上一个问题的后续:测序Scalaz WriterT 和Either with for-yield
This is a follow up to my previous question: Sequencing both Scalaz WriterT and Either with for-yield
以下代码块是使用 EitherT
和 EitherT
对 Future
、Either
和 Writer
进行排序的示例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
没有按照我的需要运行;函数 bar
和 main
函数说明了这个问题.
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
但这正是它的作用.当 fn1
和 fn2
都成功时,foo
会按照要求运行并且 main
打印出所有日志.如果 fn1
或 fn2
中的一个或两个返回 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).W
和 A
在数据中由一种类型绑定,因此它们要么大部分都在 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 的组合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!