使用Either将`flatMap`转换为`for-comprehension`

时间:2015-04-01 17:03:58

标签: scala

给出Either[String,Int]

scala> val z: Either[String, Int] = Right(100)
z: Either[String,Int] = Right(100)

我可以用flatMap编写以下代码:

scala> z.right.flatMap(x => if(x == 100) Left("foo") else Right(x))
res14: scala.util.Either[String,Int] = Left(foo)

但是,我对for comprehension版本做错了什么?

scala> for {
     |  a <- z.right
     |  _ <- if(a == 100) Left("foo") else Right(a)
     | } yield a
<console>:11: error: value map is not a member of Product with Serializable 
         with scala.util.Either[String,Int]
               _ <- if(a == 100) Left("foo") else Right(a)
                    ^

2 个答案:

答案 0 :(得分:3)

if(a == 100) Left("foo") else Right(a)Either[String, Int],而不是LeftProjectionRightProjection,因此它没有mapflatMap。你也需要预测它:

for {
    a <- z.right
    _ <- (if(a == 100) Left("foo") else Right(a)).right
} yield a

这与单线的区别在于单线相当于:

for {
    a <- z.right
} yield (if(a == 100) Left("foo") else Right(a))

..最后没有额外的map

答案 1 :(得分:1)

我认为这个问题的公认答案现在已经过时了。

因为scala 2.12Either默认为右偏。 因此,Either已定义mapflatMap

/** Binds the given function across `Left`.
 *
 *  {{{
 *  Left(12).left.flatMap(x => Left("scala")) // Left("scala")
 *  Right(12).left.flatMap(x => Left("scala")) // Right(12)
 *  }}}
 *  @param f The function to bind across `Left`.
 */
def flatMap[A1, B1 >: B](f: A => Either[A1, B1]): Either[A1, B1] = e match {
  case Left(a) => f(a)
  case _       => e.asInstanceOf[Either[A1, B1]]
}

/** Maps the function argument through `Left`.
 *
 *  {{{
 *  Left(12).left.map(_ + 2) // Left(14)
 *  Right[Int, Int](12).left.map(_ + 2) // Right(12)
 *  }}}
 */
def map[A1](f: A => A1): Either[A1, B] = e match {
  case Left(a) => Left(f(a))
  case _       => e.asInstanceOf[Either[A1, B]]
}

因此,您不必使用RightProjection,因为默认情况下Either右偏。所以下面的代码可以工作。

  val z: Either[String, Int] = Right(100)

  val ans = for {
    a <- z
    _ <- if (a == 100) Left("foo") else Right(a)
  } yield a

希望这有帮助。