for-comprehension with assignment 循环顺序错误

the for-comprehension with assignment loops in wrong order

给定 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  

---

我的问题:

  1. 为什么for循环不是按直觉顺序执行的? (我期待结果是使用 foreach 的第二个例子)
  2. 我的翻译有误吗?
  3. 为什么我们必须在 for-condition 部分赋值(例如 _ = print())? (只是一个 print() 将无法编译)

您给出的循环被翻译成以下内容:

l.map(((x) => {
  val x = println(x);
  scala.Tuple2(x, x)
})).foreach(((x) => x: @scala.unchecked match {
  case scala.Tuple2((x @ _), (x @ _)) => 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 comprehensions 比在 Haskell 中更多地基于 duck typing,可能有更多的极端情况,甚至发生在类型检查之前(据我所知) ,这会使 "mixing" 复杂化很多。

这里的问题是你对for循环的翻译(这主要来自_ = println(x)的错误翻译)。

Scala Specs可以看到(第90页):

• A generator p <- e followed by a value definition p' = e' is translated to the following generator of pairs of values, where x and x' are fresh names:

(p, p' ) <- for (x@p <- e) yield { val x'@p' = e' ; (x, x') }

逐步应用转换,您得到:

for {
  (x, u) <- for (x <- l) yield {val u = println(x); (x, u)}
  y <- x
} {println(y)}

for {
  (x,u) <- l.map {case x => val u = println(x); (x,u)}
  y <- x
} {println(y)}

l.map     { case x => val u = println(x); (x,u) }
 .foreach { case (x,u) => for ( y <- x ) {println(y)} }

l.map     { case x => val u = println(x); (x,u) }
 .foreach { case (x,u) => x.foreach {case y => println(y)} }

scala> :paste
// Entering paste mode (ctrl-D to finish)

    l.map     { case x => val u = println(x); (x,u) }
     .foreach { case (x,u) => x.foreach {case y => println(y)} }    

// Exiting paste mode, now interpreting.

List(0)
List(1)
0
1