为什么 for-comprehension 扩展为 map+foreach 而不是嵌套的 foreach?

mop*_*pot 0 foreach scala for-comprehension

我想我认为的 for-comprehension 是“为每个 'a' 创建 'x',然后为每个 'b' 对所有变量做一些事情”

for {
    a <- Seq(1, 2, 3)
    x = "test" + a
    b <- Seq(4, 5, 6)
} {
    ...
}
Run Code Online (Sandbox Code Playgroud)

应该扩展到

Seq(1, 2, 3).foreach { a =>
    val x = "test" + a
    Seq(4, 5, 6).foreach { b =>
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

但令人惊讶的是检查-Xprint:parser显示它扩展到

Seq(1, 2, 3).map { a =>
    val x = "test" + a
    (a, x)
}.foreach { case (a, x) =>
    Seq(4, 5, 6).foreach { b =>
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为它打破了对整个 for-comprehension 中发生的事情的自然理解,因为现在它首先定义三个不同的“x”,然后执行其他内容。如果“x”的定义会产生副作用,那么这可能很关键,那么脱糖的目的是什么map

Kol*_*mar 5

这种行为令人惊讶,但它记录在 Scala 规范中:https : //scala-lang.org/files/archive/spec/2.13/06-expressions.html#for-comprehensions-and-for-loops

根据那里的最后一条规则:

生成器 <- 后跟值定义 ? = ? 被转换为以下值对的生成器,其中 和 ? 是新鲜的名字:

(, ?) <- for (@ <- ) yield { val ?@? = ?; (, ?) }
Run Code Online (Sandbox Code Playgroud)

所以当有一个值定义时,Scala 总是插入一个新的 for-comprehension with a yield,然后变成map

如果您用x = "test" + agenerator替换值定义行x <- Seq("test" + a),结果将如预期的那样:

Seq(1, 2, 3)
  .foreach(((a) => Seq("test".$plus(a))
    .foreach(((x) => Seq(4, 5, 6)
      .foreach(((b) => ...))))))
Run Code Online (Sandbox Code Playgroud)