给定 val l = List( List(0), List(1) )
for循环:
for {
x <- l
_ = println(x)
y <- x
} {println(y)}
//会打印:
List(0)
List(1)
0
1
打印顺序错误!
不就是翻译成下面的吗? for循环的翻译:
l.foreach(
x => {
println(x)
x.foreach(y => println(y))
}
)
List(0)
0
List(1)
1
---
我的问题:
- 为什么 for 循环不按直观顺序执行? (我期望结果是使用 foreach 的第二个示例)
- 我的翻译有误吗?
- 为什么我们必须在 for 条件部分分配一些东西(例如
_ = print()
)? (仅print()
无法编译)
最佳答案
您给出的循环将被转换为以下内容:
l.map(((x) => {
val x$1 = println(x);
scala.Tuple2(x, x$1)
})).foreach(((x$2) => x$2: @scala.unchecked match {
case scala.Tuple2((x @ _), (x$1 @ _)) => x.foreach(((y) => println(y)))
}))
您可以通过 quasiquoting 找到类似的信息他们在口译员中。
发生的情况是,赋值与生成器之前关联起来——就像你写 for (x <- l; h = x.head)
一样。 ,这两者的耦合显然是必要的。
如果您希望每个后续生成器都发生副作用,则必须编写以下内容:
for {
x <- l
y <- {println(x); x}
} {println(y)}
这会产生您想要的打印输出,并且编译后完全符合您的预期:
l.foreach(((x) => {
println(x);
x
}.foreach(((y) => println(y)))))
至于为什么在生成器中显式丢弃参数是必要的——有两个问题。第一,println
处于不同的单子(monad)中。显然,仅为了在 monad 理解中起作用而运行表达式仅在同一个 monad 中才有意义;这在列表单子(monad)中也不是很有用。如果您在假设的 IO monad 中工作,您将能够执行以下操作:
for {
y <- readLn()
_ <- printLn(x)
}
但第二个问题来了:Scala 甚至不允许丢弃单元结果,我们仍然必须对 _
进行模式匹配。
别问我为什么,就在 the standard 里。恕我直言,允许这样做实际上是有意义的,就像 Haskell 所做的那样。然而,我能想到的一个原因正是您正在尝试做的事情:将一个单子(monad)的副作用与另一个单子(monad)的副作用混合。他们本可以想一劳永逸地禁止这一点。这也可能与 Scala 中 for 推导式的重写比 Haskell 中更多地基于鸭子类型有关,可能有更多的极端情况,甚至在类型检查之前发生(据我所知),这会使情况复杂化这样的“混合”很多。
关于scala - 赋值循环的 for 理解顺序错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29433727/