42

据我了解,Scala 的“for”语法与 Haskell 的一元“do”语法极为相似。在 Scala 中,“for”语法通常用于Lists 和Options。我想将它与Eithers 一起使用,但默认导入中不存在必要的方法。

for {
  foo <- Right(1)
  bar <- Left("nope")
} yield (foo + bar)

// expected result: Left("nope")
// instead I get "error: value flatMap is not a member..."

是否可以通过某些导入获得此功能?

有一个小问题:

for {
  foo <- Right(1)
  if foo > 3
} yield foo
// expected result: Left(???)

对于列表,它将是List(). 因为Option,它会None。Scala 标准库是否为此提供了解决方案?(或者也许scalaz?)如何?假设我想为 Either 提供我自己的“monad 实例”,我该怎么做呢?

4

3 回答 3

55

它在 scala 2.11 及更早版本中不起作用,因为Either它不是 monad。虽然有人说它是右偏,但你不能在理解中使用它:你必须得到一个LeftProjector RightProjection,如下所示:

for {
  foo <- Right[String,Int](1).right
  bar <- Left[String,Int]("nope").right
} yield (foo + bar)

顺便说一句,返回Left("nope")

在 Scalaz 上,您将替换EitherValidation. 有趣的事实:Either的原作者是 Scalaz 的作者之一 Tony Morris。他想做出Either正确的偏见,但被同事说服了。

于 2012-06-02T23:56:05.940 回答
16

是否可以通过某些导入获得此功能?

是的,但是通过第三方导入:ScalazMonadEither.

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)

现在if-guard 不是一元操作。因此,如果您尝试使用if-guard,则会按预期导致编译器错误。

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]
                              ^

上面使用的便捷方法 -.right.left- 也来自 Scalaz。

编辑:

我错过了你的这个问题。

假设我想为 Either 提供我自己的“monad 实例”,我该怎么做呢?

Scala 推导式for被简单地转换为.map, .flatMap, .withFilter, 并调用所涉及的对象。(您可以在此处找到完整的翻译方案。)因此,如果某些类没有所需的方法,您可以使用隐式转换它们添加到类中。.filter .foreach

下面是一个新的 REPL 会话。

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)
于 2012-06-03T04:43:43.090 回答
16

从 Scala 2.12 开始,现在Either正确的

文档中:

由于 Either 定义了方法 map 和 flatMap,它也可以用于理解:

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)
于 2017-06-25T20:17:50.203 回答