14

如果我用 Option 的值定义创建一个 for 理解,它会按预期工作:

scala> for (a <- Some(4); b <- Some(5); val p = a * b) yield p
res0: Option[Int] = Some(20)

如果我没有值定义,则使用 E​​ither 做同样的事情:

scala> for (a <- Right(4).right; b <- Right(5).right) yield a * b
res1: Either[Nothing,Int] = Right(20)

但是,如果我使用值定义,scala 似乎会为 for 理解推断错误的容器类型:

scala> for (a <- Right(4).right; b <- Right(5).right; val p = a * b) yield p
<console>:8: error: value map is not a member of Product with Serializable with Either[Nothing,(Int, Int)]
for (a <- Right(4).right; b <- Right(5).right; val p = a * b) yield p
                            ^

为什么这样做?有什么方法可以绕过这种行为?

4

1 回答 1

19

问题来自val p = a*b 如果你写的更简单

for (a <- Right(4).right; b <- Right(5).right) 产生 a*b

它编译,你得到正确的结果。

你的问题有两个原因

首先,Either投影mapflatMap没有通常的签名,即为例程 map 和 flatMap 定义在一个泛型类M[A]中,(A => B) => M[B]以及(A => M[B]) => M[B]。例程在M[A]is 中定义Either[A,B].RightProjection,但在结果和参数中,我们有Either[A,B]而不是投影。

其次,val p = a*bfor 理解中的方式是翻译的。Scala 参考,第 6.19 页,第 90 页:

生成器 p <- e 后跟值定义 p' = e' 被转换为以下值对生成器,其中 x 和 x' 是新名称:

(p,p′) <- for(x@p<-e) yield {val x′@p′ = e′; (x,x′)}

让我们稍微简化一下代码,去掉a <-. 此外,b并用forp重命名为pandpp更接近重写规则。应该在范围 for(p <- Right(5).right; val pp = a*p) yield ppppp'a

按照规则,我们必须替换生成器+定义。周围是什么,for(并且)yield pp,没有变化。

for((p, pp) <- for(x@p <- Right(5).right) yield{val xx@pp = a*p; (x,xx)}) yield pp

内部的 for 被重写为一个简单的 map

for((p, pp) <- Right(5).right.map{case x@p => val xx@pp = a*p; (x,xx)}) yield pp

这是问题所在。是Right(5).right.map(...)type Either[Nothing, (Int,Int)],不是Either.RightProjection[Nothing, (Int,Int)]我们想要的。它在外部 for 中不起作用(它也转换为 a map。没有map方法 on Either,它仅在投影上定义。

如果您仔细查看您的错误消息,它会这样说,即使它提到Productand Serializable,它也会说它是一个Either[Nothing, (Int, Int)],并且上面没有定义任何映射。该对(Int, Int)直接来自重写规则。

for 理解旨在在尊重正确签名时很好地工作。借助Either投影技巧(也有其优点),我们遇到了这个问题。

于 2011-09-02T23:15:37.913 回答