Scala的Either.RightProjection混淆(用于理解for推导式)

12

我可以在Scala的for循环推导中使用=(如SLS第6.19节所指定的),示例如下:

选项

假设我有一个函数String => Option[Int]

scala> def intOpt(s: String) = try { Some(s.toInt) } catch { case _ => None }
intOpt: (s: String)Option[Int]

然后我可以这样使用它
scala> for {
   |     str <- Option("1")
   |     i <- intOpt(str)
   |     val j = i + 10    //Note use of = in generator
   |   }
   |   yield j
res18: Option[Int] = Some(11)

我的理解是这与以下内容基本相当:

scala> Option("1") flatMap { str => intOpt(str) } map { i => i + 10 } map { j => j }
res19: Option[Int] = Some(11)

也就是说,嵌入式生成器是一种将 map 注入到一系列 flatMap 调用中的方法。目前为止还不错。

Either.RightProjection

我想要做的实际上是:使用类似于前面例子中使用的for-comprehension,但是这次使用Either单子类型。
然而,如果我们在类似的链中使用Either.RightProjection单子/函子,它不起作用。
scala> def intEither(s: String): Either[Throwable, Int] = 
  |      try { Right(s.toInt) } catch { case x => Left(x) }
intEither: (s: String)Either[Throwable,Int]

然后使用:
scala> for {
 | str <- Option("1").toRight(new Throwable()).right
 | i <- intEither(str).right //note the "right" projection is used
 | val j = i + 10
 | }
 | yield j
<console>:17: error: value map is not a member of Product with Serializable with Either[java.lang.Throwable,(Int, Int)]
              i <- intEither(str).right
                ^

这个问题与右投影所期望的作为其flatMap方法参数的函数有关(即它期望一个R => Either[L, R])。但是,即使修改第二个生成器以不调用right,它仍无法编译。
scala>  for {
 |        str <- Option("1").toRight(new Throwable()).right
 |        i <- intEither(str) // no "right" projection
 |          val j = i + 10
 |      }
 |      yield j
<console>:17: error: value map is not a member of Either[Throwable,Int]
              i <- intEither(str)
                            ^

巨大混乱

但是现在我变得更加困惑了。以下代码完全正常运行:

scala> for {
 |       x <- Right[Throwable, String]("1").right
 |       y <- Right[Throwable, String](x).right //note the "right" here
 |     } yield y.toInt
res39: Either[Throwable,Int] = Right(1)

但这个不会:
scala> Right[Throwable, String]("1").right flatMap { x => Right[Throwable, String](x).right } map { y => y.toInt }
<console>:14: error: type mismatch;
 found   : Either.RightProjection[Throwable,String]
 required: Either[?,?]
              Right[Throwable, String]("1").right flatMap { x => Right[Throwable, String](x).right } map { y => y.toInt }
                                                                                             ^

我认为它们是等价的

  • 发生了什么?
  • 如何在跨越 Either 的 for 推导中嵌入一个 = 生成器?

小注:在for-comprehension中,您不需要使用val。只需使用j = i + 10即可正常工作。但是,我认为没有理由不将计算放在yield的右侧:} yield i + 10。同样地,在第一个map示例中,map { j => j }没有任何作用,可以省略。 - user unknown
我会这样定义intOptintEitherdef intOpt(s: String) = allCatch opt s.toIntdef intEither(s: String) = allCatch either s.toInt(在导入scala.util.control.Exception._之后)。 - Jean-Philippe Pellet
@user unknown - 这只是一个例子;在我的使用场景中,我确实想将一个 map 嵌入到一系列的 flatMaps 中。 - oxbow_lakes
1个回答

9

您无法在for-comprehension中嵌入=Jason Zaugg报告的此问题有关;解决方案是右偏Either(或创建一个等同于它的新数据类型)。

对于您的极度困惑,您错误地扩展了for sugar。其解糖过程如下:

for {
  b <- x(a)
  c <- y(b)
} yield z(c)

x(a) flatMap { b =>
 y(b) map { c =>
  z(c) }} 

而不是

x(a) flatMap { b => y(b)} map { c => z(c) }

因此,你应该这样做:
scala> Right[Throwable, String]("1").right flatMap { x => Right[Throwable, String](x).right map { y => y.toInt } }
res49: Either[Throwable,Int] = Right(1)

关于解糖化的更多乐趣(`j = i + 10`问题)

for {
  b <- x(a)
  c <- y(b)
  x1 = f1(b)
  x2 = f2(b, x1)
  ...
  xn = fn(.....)
  d <- z(c, xn)
} yield w(d)

被解糖成

x(a) flatMap { b =>
  y(b) map { c =>
    x1 = ..
    ...
    xn = ..
    (c, x1, .., xn) 
  } flatMap { (_c1, _x1, .., _xn) =>
    z(_c1, _xn) map w }}

所以在你的情况下,y(b) 的结果类型为 Either,它没有定义 map

作为一种解决方法,我用“x <- Right(y).right”替换右侧的推导式中的“x = y”。虽然不完美,但对于我很少使用它的情况来说还可以。 - Jürgen Strobel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接