如果我使用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
^
为什么这样做?有哪些方法可以解决这种问题?
答案 0 :(得分:19)
问题来自val p = a*b
如果你写的更简单
for(a&lt;-Right(4).right; b&lt;-Right(5).right)a * b
它编译,你得到了正确的结果。
您的问题有两个原因
首先,Either
投影map
和flatMap
没有通常的签名,即在泛型类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
重命名为p
,p
更接近重写规则,pp
为pp
。 p'
应该在范围内
for(p < - Right(5).right; val pp = a * p)yield pp
遵循规则,我们必须替换生成器+定义。那是什么,a
和for(
,没有变化。
)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
方法,它仅在投影上定义。
如果仔细查看错误消息,就会这样说,即使它提到Either
和Product
,也会说它是Serializable
,并且没有定义地图在上面。对Either[Nothing, (Int, Int)]
直接来自重写规则。
理解是为了在尊重正确的签名时运作良好。通过(Int, Int)
投影(也有其优势)的技巧,我们遇到了这个问题。