我们使用 Scalafor
表达式,并迭代具有多个循环的元素,例如我们的First 表达式代码是:
for {
a <- 1 to 10
_ = print(a)
b <- 11 to 20
_ = print(b)
} yield 1
根据 scala,for
表达式包含a <- 1 to 10
等表达式,如果存在多个表达式,则将它们视为内部循环。我们假设上面的代码与我们的第二个表达式相同:
for(a <- 1 to 10) {
print(a + " --- ")
for(b <- 11 to 20) {
print(b + " === ")
}
}
但是两个代码的输出是不同的。两个表达式代码的预期输出如下所示:
1 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 2 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 3 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 4 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 5 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 6 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 7 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 8 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 9 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===
但此输出仅由秒for
表达式生成。第一个表达式给了我们意想不到的输出。
1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===
此输出首先打印1 2 .. 10
元素,然后11 12 ... 20
元素 10 次。为什么这两个输出不同?
第二件事,同时使用另一个for
表达式:
for {
a <- 1 to 10
_ = print(a + " --- ")
b <- 11 to 20
_ = print(b + " === " + a + " ")
} yield 1
输出为:
1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 1 12 === 1 13 === 1 14 === 1 15 === 1 16 === 1 17 === 1 18 === 1 19 === 1 20 === 1 11 === 2 12 === 2 13 === 2 14 === 2 15 === 2 16 === 2 17 === 2 18 === 2 19 === 2 20 === 2 11 === 3 12 === 3 13 === 3 14 === 3 15 === 3 16 === 3 17 === 3 18 === 3 19 === 3 20 === 3 11 === 4 12 === 4 13 === 4 14 === 4 15 === 4 16 === 4 17 === 4 18 === 4 19 === 4 20 === 4 11 === 5 12 === 5 13 === 5 14 === 5 15 === 5 16 === 5 17 === 5 18 === 5 19 === 5 20 === 5 11 === 6 12 === 6 13 === 6 14 === 6 15 === 6 16 === 6 17 === 6 18 === 6 19 === 6 20 === 6 11 === 7 12 === 7 13 === 7 14 === 7 15 === 7 16 === 7 17 === 7 18 === 7 19 === 7 20 === 7 11 === 8 12 === 8 13 === 8 14 === 8 15 === 8 16 === 8 17 === 8 18 === 8 19 === 8 20 === 8 11 === 9 12 === 9 13 === 9 14 === 9 15 === 9 16 === 9 17 === 9 18 === 9 19 === 9 20 === 9 11 === 10 12 === 10 13 === 10 14 === 10 15 === 10 16 === 10 17 === 10 18 === 10 19 === 10 20 === 10
a
的期望值存在于内部循环中。我们仍然对这种行为感到困惑。这种行为的共鸣是什么?
原因是:
for {
a <- 1 to 10
_ = print(a)
b <- 11 to 20
_ = print(b)
} yield 1
相当于:
(1 to 10).
map{a => print(a); a}.
flatMap{a =>
(11 to 20).
map{b => print(b); 1}
}
您会看到,由于1 to 10
是一个集合,因此映射它将立即构建新集合并执行所有print
语句。 如果希望仅按需执行print(a)
,则可以将1 to 10
更改为(1 to 10).view
。
第二个循环等效于:
(1 to 10).foreach { a =>
print(a + " --- ")
(11 to 20).foreach { b =>
print(b + " === ")
}
}
这解释了为什么它会在所有值之前打印a
b
您的第二个问题相当于:
(1 to 10).map{a => print(a + " --- "); a}.
flatMap{ a =>
(11 to 20).map{ b =>
print(b + " === " + a + " ")
1
}
}
希望输出现在有意义。
你应该读这个:http://docs.scala-lang.org/tutorials/FAQ/yield.html,这个:http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#for-comprehensions-and-for-loops。
简而言之,您的第一个表达式和第二个表达式实际上非常不同,因为一个使用yield
,而另一个不使用。具有yield
的称为"for-comprehension",另一个称为"for-loop"。
For-comprehention表示为map
和flatMap
的组合(加上一些与当前讨论无关的其他结构(,for-loop表示为foreach
。
也就是说,编译器将你的第一个表达式转换为:
scala.Predef
.intWrapper(1)
.to(10)
.map[(Int, Unit), scala.collection.immutable.IndexedSeq[(Int, Unit)]]((a: Int) => {
val x$1 = scala.Predef.print(a.+(" --- "));
scala.Tuple2.apply[Int, Unit](a, x$1)
})(scala.collection.immutable.IndexedSeq.canBuildFrom[(Int, Unit)])
.flatMap[Int, Any]((x$4: (Int, Unit)) =>
(x$4: @scala.unchecked) match {
case scala.Tuple2((a @ _), _) =>
scala.Predef
.intWrapper(11)
.to(20)
.map[(Int, Unit), scala.collection.immutable.IndexedSeq[(Int, Unit)]]((b: Int) => {
val x$2 = scala.Predef.print(b.+(" === "));
scala.Tuple2.apply[Int, Unit](b, x$2)
})(scala.collection.immutable.IndexedSeq.canBuildFrom[(Int, Unit)])
.map[Int, scala.collection.immutable.IndexedSeq[Int]]((x$3: (Int, Unit)) =>
(x$3: @scala.unchecked) match {
case scala.Tuple2((b @ _), _) => 1
})(scala.collection.immutable.IndexedSeq.canBuildFrom[Int])
})(scala.collection.immutable.IndexedSeq.canBuildFrom[Int])
可以简化为:
(1 to 10).map { a =>
val x = print(a + " --- ")
(a, x)
}.flatMap { case (_, _) =>
(11 to 20).map { b =>
val x = print(b + " === ")
(b, x)
}.map { _ =>
1
}
}
而第二个表达式为:
(1 to 10).foreach(a => {
print(a + " --- ")
(11 to 20).foreach(b => print(b + " === "))
})
在这里,表达式之间的差异很容易看出。无论如何,我都会通过解释,特别是对于第一个表达式,因为它是最复杂的。
在第一个表达式中,首先执行此map
:
(1 to 10).map { a =>
val x = print(a + " --- ")
(a, x)
}
它打印1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 ---
,正如预期的那样。现在,该表达式的结果是具有以下元素的集合:Tuple2[Int, Unit]
,生成的集合包含其中的 10 个:(1, ()), (2, ()), ... (10, ())
。
flatMap
在该中间集合上执行。因此,flatMap
下的函数执行 10 次,并打印 10 次以下内容:11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===
。
最终结果与问题相同,即第一个表达式打印:
1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===
我不会介绍第二个表达式,因为它要简单得多,并且表示 OP 预期的行为。
所以,
for {
a <- 1 to 10
_ = print("something")
b <- 11 to 20
_ = print("something else")
} yield 1
相当于:
(1 to 10).flatMap { a =>
print("something")
(11 to 20).map { b =>
print("something else")
1
}
}
现在,此循环的行为将取决于您用于第一个flatMap
的集合类型。scala 中的一些集合是"懒惰的",其他的则不是。 "非懒惰"的集合是那些,当你这样做时foo.flatMap { x => ... }
会立即计算整个集合的函数,并返回一个包含结果的新集合。惰性集合就是一个,它将在访问元素时逐个评估转换。
Seq(1,2,3,4)
.map { n => println("foo" + n); n + 1 }
.map { n => println("bar" + n); n - 1 }
这将打印:
foo1
foo2
foo3
foo4
bar2
bar3
bar4
bar5
所有元素首先通过第一个地图,然后通过下一个地图。
另一方面,这:
Iterator(1,2,3,4)
.map { n => println("foo" + n); n + 1 }
.map { n => println("bar" + n); n - 1 }
根本不打印任何内容。 如果在生成的Iterator
上调用.next
,您将看到
foo1
bar2
因此,它通过两个映射发送第一个元素,然后再转到第二个映射。
同样的事情也会发生在你的循环中:(1 to 10)
是一个Range
,这是一个"渴望"的集合。这意味着,在进行2
之前,for
理解的整个主体都会被评估为1
。
您可以通过使集合"懒惰"来将行为切换到其他代码段:a <- (1 to 10).iterator
或a <- (1 to 10).toStream
但是现在,for
理解的结果也将是懒惰的 - 它将与列表中的第一个集合的类型相同,并且,为了看到整个打印出来的东西,你必须"强制"评估 al 元素,例如,通过调用结果.toList
。