As I understand it, Scala "for" syntax is extremely similar to Haskell's monadic "do" syntax. In Scala, "for" syntax is often used for List
s and Option
s. I'd like to use it with Either
s, but the necessary methods are not present in the default imports.
for {
foo <- Right(1)
bar <- Left("nope")
} yield (foo + bar)
// expected result: Left("nope")
// instead I get "error: value flatMap is not a member..."
Is this functionality available through some import?
There is a slight hitch:
for {
foo <- Right(1)
if foo > 3
} yield foo
// expected result: Left(???)
For a List, it would be List()
. For Option
, it would be None
. Do the Scala standard libraries provide a solution to this? (Or perhaps scalaz
?) How? Suppose I wanted to provide my own "monad instance" for Either, how could I do that?
It doesn't work in scala 2.11 and earlier because Either
is not a monad. Though there's talk of right-biasing it, you can't use it in a for-comprehension: you have to get a LeftProject
or RightProjection
, like below:
for {
foo <- Right[String,Int](1).right
bar <- Left[String,Int]("nope").right
} yield (foo + bar)
That returns Left("nope")
, by the way.
On Scalaz, you'd replace Either
with Validation
. Fun fact: Either
's original author is Tony Morris, one of Scalaz authors. He wanted to make Either
right-biased, but was convinced otherwise by a colleague.
Is this functionality available through some import?
Yes, but via a third party import: Scalaz provides a Monad
instance for Either
.
import scalaz._, Scalaz._
scala> for {
| foo <- 1.right[String]
| bar <- "nope".left[Int]
| } yield (foo.toString + bar)
res39: Either[String,java.lang.String] = Left(nope)
Now if
-guard is not a monadic operation. Therefore if you attempt to use if
-guard, it results in a compiler error as expected.
scala> for {
| foo <- 1.right[String]
| if foo > 3
| } yield foo
<console>:18: error: value withFilter is not a member of Either[String,Int]
foo <- 1.right[String]
^
The convenience methods used above - .right
and .left
- are also from Scalaz.
Edit:
I missed this question of yours.
Suppose I wanted to provide my own "monad instance" for Either, how could I do that?
Scala for
comprehensions are simply translated to .map
, .flatMap
, .withFilter
, and .filter
.foreach
calls on the objects involved. (You can find the the full translation scheme here.) So if some class does not have the required methods, you can add them to a class using implicit conversions.
A fresh REPL session below.
scala> implicit def eitherW[A, B](e: Either[A, B]) = new {
| def map[B1](f: B => B1) = e.right map f
| def flatMap[B1](f: B => Either[A, B1]) = e.right flatMap f
| }
eitherW: [A, B](e: Either[A,B])java.lang.Object{def map[B1](f: B => B1): Product
with Either[A,B1] with Serializable; def flatMap[B1](f: B => Either[A,B1]):
Either[A,B1]}
scala> for {
| foo <- Right(1): Either[String, Int]
| bar <- Left("nope") : Either[String, Int]
| } yield (foo.toString + bar)
res0: Either[String,java.lang.String] = Left(nope)
As of Scala 2.12, Either
is now right biased
From the documentation:
As Either defines the methods map and flatMap, it can also be used in for comprehensions:
val right1: Right[Double, Int] = Right(1) val right2 = Right(2) val right3 = Right(3) val left23: Left[Double, Int] = Left(23.0) val left42 = Left(42.0) for ( a <- right1; b <- right2; c <- right3 ) yield a + b + c // Right(6) for ( a <- right1; b <- right2; c <- left23 ) yield a + b + c // Left(23.0) for ( a <- right1; b <- left23; c <- right2 ) yield a + b + c // Left(23.0) // It is advisable to provide the type of the “missing” value (especially the right value for `Left`) // as otherwise that type might be infered as `Nothing` without context: for ( a <- left23; b <- right1; c <- left42 // type at this position: Either[Double, Nothing] ) yield a + b + c // ^ // error: 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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With