Scala for-comprehension 从多个变量绑定中产生结果

Scala for-comprehension yielding result from multiple variable bindings

我很好奇 Scala 是如何去除以下理解糖分的:

for {
  a <- Option(5)
  b = a * 2
  c <- if (b == 10) Option(100) else None
} yield b + c

我的困难在于产量中同时包含 bc,因为它们似乎在不同的步骤中绑定

这两个代码是等价的:

scala> for {
     |   a <- Option(5)
     |   b = a * 2
     |   c <- if (b == 10) Option(100) else None
     | } yield b + c
res70: Option[Int] = Some(110)

scala> for {
     |   a <- Option(5)
     |   b = a * 2
     |   if (b == 10) 
     |   c <- Option(100) 
     | } yield b + c
res71: Option[Int] = Some(110)

由于不涉及集合,产生多个值,因此只有一大步 - 或者,有争议的是 3 到 4 小步。如果 a 为 None,则整个循环将提前终止,产生 None。

脱糖是flatMap/withFilter/map。

你甚至可以问编译器。以下命令:

scala -Xprint:parser -e "for {
  a <- Option(5)
  b = a * 2
  c <- if (b == 10) Option(100) else None
} yield b + c"

产生这个输出

[[syntax trees at end of                    parser]] // scalacmd7617799112170074915.scala
package <empty> {
  object Main extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    def main(args: Array[String]): scala.Unit = {
      final class $anon extends scala.AnyRef {
        def <init>() = {
          super.<init>();
          ()
        };
        Option(5).map(((a) => {
  val b = a.$times(2);
  scala.Tuple2(a, b)
})).flatMap(((x) => x: @scala.unchecked match {
          case scala.Tuple2((a @ _), (b @ _)) => if (b.$eq$eq(10))
  Option(100)
else
  None.map(((c) => b.$plus(c)))
        }))
      };
      new $anon()
    }
  }
}

只取你感兴趣的部分并提高可读性,你会得到:

Option(5).map(a => {
  val b = a * 2
  (a, b)
}).flatMap(_ match {
  case (_, b) => 
    if (b == 10)
      Option(100)
    else
      None.map(c => b + c)
})

编辑

正如评论中所报告的那样,从编译器输出中进行字面翻译似乎突出了如何呈现脱糖表达式的错误。总和应该 map 根据 if 表达式的结果,而不是 else 分支中的 None

Option(5).map(a => {
  val b = a * 2
  (a, b)
}).flatMap(_ match {
  case (_, b) => 
    (if (b == 10) Option(100) else None).map(c => b + c)
})

如果这是一个错误,询问编译器团队可能是值得的。

这是 desugar 的净化输出 - Ammonite REPL 中可用的命令:

Option(5)
  .map { a =>
    val b = a * 2;
    (a, b)
  }
  .flatMap { case (a, b) =>
      (if (b == 10) Option(100) else None)
        .map(c => b + c)
  }

bc 都可以出现在 yield 中,因为它不会对 map/flatMap 的 链式 调用脱糖,而是 嵌套 调用。