为什么Scala为涉及Either和值定义的'for'表达式选择“Product”类型

时间:2011-09-02 22:06:19

标签: scala type-inference for-comprehension

如果我使用Option创建带有值定义的for comprehension,它将按预期工作:

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

如果我没有值定义,则执行相同操作:

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

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

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
                            ^

为什么这样做?有哪些方法可以解决这种问题?

1 个答案:

答案 0 :(得分:19)

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

for(a&lt;-Right(4).right; b&lt;-Right(5).right)a * b

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

您的问题有两个原因

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

其次,翻译中的Either[A,B]理解方式。 Scala参考,6.19 p 90:

  

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

val p = a*b

让我们稍微简化一下代码,放弃(p,p′) <- for(x@p<-e) yield {val x′@p′ = e′; (x,x′)} 。此外,a <-b重命名为pp更接近重写规则,ppppp'应该在范围内 for(p < - Right(5).right; val pp = a * p)yield pp

遵循规则,我们必须替换生成器+定义。那是什么,afor(,没有变化。

)yield pp

将内部for重写为简单的地图

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

这是问题所在。 for((p, pp) <- Right(5).right.map{case x@p => val xx@pp = a*p; (x,xx)}) yield pp 的类型为Right(5).right.map(...),而不是我们想要的Either[Nothing, (Int,Int)]。它在外部不起作用(也转换为Either.RightProjection[Nothing, (Int,Int)]map上没有map方法,它仅在投影上定义。

如果仔细查看错误消息,就会这样说,即使它提到EitherProduct,也会说它是Serializable,并且没有定义地图在上面。对Either[Nothing, (Int, Int)]直接来自重写规则。

理解是为了在尊重正确的签名时运作良好。通过(Int, Int)投影(也有其优势)的技巧,我们遇到了这个问题。