该使用哪个Monad Transformer?

4

我试着写了下面的验证函数,以便在遇到第一个错误后停止验证。其中three函数的返回类型与其他函数不同。请问我该使用哪个单子变换器使得这段代码能够编译?

import scalaz._
import Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global


def one(a : String): Disjunction[Int, String] =
  a == "one" match {
    case true => \/-("one")
    case false => -\/(2)
  }

def two(a : String): Disjunction[Int, String] =
  a == "two" match {
    case true => \/-("two")
    case false => -\/(3)
  }

def three(a : String): Future[Disjunction[Int, String]] =
  Future (a == "three") map {
    case true => \/-("three")
    case false => -\/(4)
  }

def validate(a : String) = for {
  e1 <- one(a)
  e2 <- two(a)
  e3 <- EitherT(three(a))
} yield (e1 |+| e2 |+| e3)

编译错误:

Error:(27, 7) type mismatch;
 found   : scalaz.EitherT[scala.concurrent.Future,Int,String]
 required: scalaz.\/[?,?]
  e3 <- EitherT(three(a))
     ^
Error:(66, 7) type mismatch;
 found   : scalaz.EitherT[scala.concurrent.Future,Int,String]
 required: scalaz.\/[?,?]
  e3 <- EitherT(three(a))
     ^

1
你有没有任何理由不将 onetwo 包装在 Future 中,或者等待 three 的结果? - Bruno Grieder
你想要看一下 EitherT 单子变换器,第三个 <- 语句返回的不是 String,而是整个 Disjunction,这可能不是你想要的(前两个访问了 disjunction 的 String 部分)。 - Ende Neu
@EndeNeu 我尝试了EitherT,但仍然出现编译错误。请查看更新的问题以及编译错误。我之前使用的是DisjunctionT(它只是EitherT的包装器),结果相同。 - user3248346
2个回答

6
在这种情况下,您可以采取两种一般方法。第一种是使所有方法返回您要使用的堆栈(在本例中为EitherT [Future,Int,?]),或者每个单独的方法都返回最准确地捕获其自身效果的类型,然后在组合它们时适当地引发您获得的值。
如果您知道使用方式的确切外观,则第一种方法可以使使用更具语法方便,但后一种方法更灵活,在我看来通常是更好的选择。在您的情况下,它应该是这样的:
import scalaz._, Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

def one(a: String): Disjunction[Int, String] = (a == "one").either("one").or(2)
def two(a: String): Disjunction[Int, String] = (a == "two").either("two").or(3)

def three(a: String): EitherT[Future, Int, String] = EitherT(
  Future(a == "three").map(_.either("three").or(4))
)

def validate(a: String) = for {
  e1 <- EitherT.fromDisjunction[Future](one(a))
  e2 <- EitherT.fromDisjunction[Future](two(a))
  e3 <- three(a)
} yield (e1 |+| e2 |+| e3)

接着:

scala> validate("one").run.foreach(println)
-\/(3)

scala> validate("x").run.foreach(println)
-\/(2)

如果你有一个普通的 Future 并且想在 for-comprehension 中使用它,你可以用 .liftM[EitherT[?[_], String, ?]] 将它提升为 EitherT[Future, String, A]
(请注意,这个方法可能并不是非常有用,因为它永远不会成功(一个字符串不能同时等于"one""two""three"),但至少组合是成立的。)
关于如何更普遍地选择单子变换器堆栈:你只需要将类型里面翻转过来,使得 Future[Disjunction[Int, ?]] 变成 EitherT[Future, Int, ?] 等等。在这种情况下,具体而言,Future 没有一个单子变换器(它不可遍历,并且无法实现 FutureT 而不阻塞),所以你知道它必须在内部。

2

“|” 运算符能否在 Play 控制器之外使用? - Alexandre Annic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接