Scala Either question


#1
   val left42 : Left[Double, Nothing]   = Left(42.0)
   val left23: Left[Double, Int]  = Left(23.0)
   val result = for {
       a <- left23
       b <- left42
    } yield(a + b)

Why the above code gives the following compilation error ?

Error:(37, 19) ambiguous reference to overloaded definition,
both method + in class Int of type (x: Char)Int
and method + in class Int of type (x: Byte)Int
match argument types (Nothing)
} yield(a + b)


#2

That code may not mean what you intend. Either is “right-biased”, where the right-hand side is the “real” one that propagates down through for; the left-hand side is typically used for error conditions. So

b <- left42

means b has type Nothing (since that is the right-hand side of left42), and + doesn’t exist on Nothing.

I suspect you want your lefts and rights to be reversed, but I’m not sure what the intent of the above code is…


#3

If “left42” is treated as Nothing because Either is right biased as you said then how come the following works.

   val left42 : Left[Double, Int]   = Left(42.0)
   val left23: Left[Double, Int]  = Left(23.0)
   val result = for {
       a <- left23
       b <- left42
    } yield(a + b)

result: scala.util.Either[Double,Int] = Left(23.0)

All I changed was the type and it has Left[Double,Int] now for left42


#4

Because Int has a + operator, so this version of the code is legal. Your first version fails only because you explicitly typed it as Nothing.

Note that your + only applies to the right-hand side, and your example here shows that: it gets to:

a <- left23

and since the value passed in is a Left, it skips the line:

b <- left42

and returns the value of left23.

But the code still needs to compile, and your original example up top was trying to add an Int and a Nothing, which doesn’t make any sense, so the compiler rejects it.

Basically, the left-hand side of an Either is not usually set – when it does get set, that generally indicates an error, so all further processing gets skipped and it just returns the first Left it encounters. The for comprehension normally expects you to be composing Rights instead. The type of a and b will always be the type of the right-hand side of the Either. So the yield has to make sense with those types.

NB: all of the above applies only to Scala 2.12 and later. In 2.11 and before, Either was un-biased, and you couldn’t simply use it like this in a for comprehension – you had to explicitly say whether you wanted to use the left or right. But at this point, the right-hand side should always be used for the real data, and the left-hand side for the “short-circuiting” data, usually an error code of some sort.


#5

Thanks. I understand that now. Now my question is that the computation in the yield part (i.e a + b) is not going to have any impact at all in the for comprehension because we have both “Left” in the for comprehension. It does not matter and whatever the computation in the yield part the result is going to be just Left(23.0) right ? Then why the heck the compiler spits the error ? Yeah, I completely understand it takes the right values for computation. If there are no any Right values at all as in our case above what is the point in the computation ? I know nobody is going to do this way in real life. But my argument is that if the compiler sees that all are Left (or) at least one is Left in the for comprehension then it should ignore the computation in the yield part because the computation is not going to happen and not going to have any impact in the final result.
Please correct me if I am wrong.


#6

Well, you’re wrong. If compiler sees following code:

if (2 + 2 == 5) {
  here is some garbage that can't compile
}

Then it signals compilation error even if it’s obvious that the code inside the if would never have the chance to execute. Same goes for everything else, i.e. all the code must be syntactically correct and have correct types.


#7

Well, the code is in error. You’re expecting way too much brilliance from the compiler (discussed below), but it is Just Plain Broken – that yield can’t possibly work in your first example, so flagging it as an error makes sense. The error message isn’t quite as clear as you might wish, but it’s 100% correct.

I’m not an expert in the compiler, but here’s the way I believe this is playing out:

First, keep in mind that for comprehensions know nothing whatsoever about Left per se; indeed, for doesn’t really exist. for is just a bit of syntax sugar that gets translated into function calls, and will work for any type that has map and flatMap defined. Left does define those, even though they simply fall through, so the compiler uses them to build the actual code that gets compiled:

   val result = left23.flatMap { a: Int =>
      left42.map { b: Nothing =>
         a + b
      }
   }

That’s essentially the code that is actually being compiled – the for gets rewritten to that.

The compiler knows nothing whatsoever about the fact that Left falls through – it’s just another type that has map and flatMap, like hundreds of others. Keep in mind, Left is just a type. It isn’t defined by the compiler, it’s defined in the standard library, and the compiler intentionally avoids knowing too much about the standard library. (People complain enough about the speed of the compiler – it would be vastly worse if it tried to be clever about special cases like this.)

What the compiler does know, though, is that a + b is illegal because you can’t add Nothing to Int – the + operator isn’t defined for that. So that’s the error you actually get.


#8

Agree. Thank You.