在 Scalaz 中自定义 Future、Either 和 Writer 的组合
Customising composition of Future, Either and Writer in Scalaz
这是我之前问题的后续:
以下代码块是使用 EitherT
和 WriterT
monad 转换器对 Future
、Either
和 Writer
进行排序的示例;下面的问题是关于如何巧妙地改变那堆变压器的行为。
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
函数说明了这个问题。
所需的行为是 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
但这正是它的作用。当 fn1
和 fn2
都成功时,foo
将按要求运行并且 main
打印出所有日志。如果 fn1
或 fn2
return a Left
函数 bar
returns 没有日志并且 main 继续仅打印异常.无法查看日志中的进度。
似乎这个特定的转换器堆栈的行为方式是,如果序列中有 -\/
,则日志记录上下文被简单地映射出来...
查看 WriterT
的 Scalaz 代码,情况可能是这样:
final case class WriterT[F[_], W, A](run: F[(W, A)])
WriterT
是一个案例 class,其唯一成员是 run
。对于这个例子,run
是我们的日志上下文 (A
) 和我们的结果的元组,它们都包含在相同的 EitherT
(F
) 中。 W
和 A
按类型绑定在数据中,因此它们要么都在左内,要么都在右内。
我可以推测我需要一个行为略有不同的 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
类型 class 是否是解决此问题并实现我想要的行为的明智方法。
我有哪些选择?
这篇文章: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) a
同构于 Either 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)
}
}
这是我之前问题的后续:
以下代码块是使用 EitherT
和 WriterT
monad 转换器对 Future
、Either
和 Writer
进行排序的示例;下面的问题是关于如何巧妙地改变那堆变压器的行为。
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
函数说明了这个问题。
所需的行为是 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
但这正是它的作用。当 fn1
和 fn2
都成功时,foo
将按要求运行并且 main
打印出所有日志。如果 fn1
或 fn2
return a Left
函数 bar
returns 没有日志并且 main 继续仅打印异常.无法查看日志中的进度。
似乎这个特定的转换器堆栈的行为方式是,如果序列中有 -\/
,则日志记录上下文被简单地映射出来...
查看 WriterT
的 Scalaz 代码,情况可能是这样:
final case class WriterT[F[_], W, A](run: F[(W, A)])
WriterT
是一个案例 class,其唯一成员是 run
。对于这个例子,run
是我们的日志上下文 (A
) 和我们的结果的元组,它们都包含在相同的 EitherT
(F
) 中。 W
和 A
按类型绑定在数据中,因此它们要么都在左内,要么都在右内。
我可以推测我需要一个行为略有不同的 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
类型 class 是否是解决此问题并实现我想要的行为的明智方法。
我有哪些选择?
这篇文章: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) a
同构于 Either 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)
}
}