Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Scala choose the type 'Product' for 'for' expressions involving Either and value definitions

If I create a for comprehension with a value definition with Option, it works as expected:

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

Doing the same thing with Either works if i have no value definition:

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

But if i used the value definition, scala seems to infer the wrong container type for the 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
                            ^

Why does it do this? What ways around this behavior are available?

like image 685
srparish Avatar asked Sep 02 '11 22:09

srparish


1 Answers

The problems comes from val p = a*b If you write the simpler

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

it compiles and you get the proper result.

Your problem has two causes

First, the Either projections map and flatMap do not have the usual signature , namely for routines map and flatMap defined in a generic class M[A], (A => B) => M[B] and (A => M[B]) => M[B]. The M[A] the routine are defined in is Either[A,B].RightProjection, but in results and argument, we have Either[A,B] and not the projection.

Second, the way val p = a*b in the for comprehension is translated. Scala Reference, 6.19 p 90:

A generator p <- e followed by a value definition p′ = e′ is translated to the following generator of pairs of values, where x and x′ are fresh names:

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

Let's simplify the code just a little bit, dropping the a <-. Also, b and p renamed to p and pp to be closer to the rewrite rule, with pp for p'. a supposed to be in scope for(p <- Right(5).right; val pp = a*p) yield pp

following the rule, we have to replace the generator + definition. What is around that, for( and )yield pp, unchanged.

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

The inner for is rewritten to a simple map

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

Here is the problem. The Right(5).right.map(...) is of type Either[Nothing, (Int,Int)], not Either.RightProjection[Nothing, (Int,Int)] as we would want. It does not work in the outer for (which converts to a map too. There is no map method on Either, it is defined on projections only.

If you look closely at your error message, it says so, even if it mentions Product and Serializable, it says that it is an Either[Nothing, (Int, Int)], and that no map is defined on it. The pair (Int, Int) comes directly from the rewrite rule.

The for comprehension is intended to work well when respecting the proper signature. With the trick with Either projections (which has its advantages too), we get this problem.

like image 156
Didier Dupont Avatar answered Nov 17 '22 13:11

Didier Dupont