Pao*_*lla 11 closures loops scala
正如Eric Lippert的博客文章中所讨论的那样,关闭循环变量被认为是有害的,在C#中关闭循环变量会产生意想不到的后果.我试图理解是否将同样的"问题"应用于Scala.
首先,由于这是一个Scala问题,我将尝试解释Eric Lippert的C#示例,在他的代码中添加一些注释
// Create a list of integers
var values = new List<int>() { 100, 110, 120 };
// Create a mutable, empty list of functions that take no input and return an int
var funcs = new List<Func<int>>();
// For each integer in the list of integers we're trying
// to add a function to the list of functions
// that takes no input and returns that integer
// (actually that's not what we're doing and there's the gotcha).
foreach(var v in values)
funcs.Add( ()=>v );
// Apply the functions in the list and print the returned integers.
foreach(var f in funcs)
Console.WriteLine(f());
Run Code Online (Sandbox Code Playgroud)
大多数人都希望这个程序打印100,110,120.它实际打印120,120,120.问题是() => v我们添加到funcs列表中的函数关闭了v 变量,而不是v的值.当v改变值时,在第一个循环中,我们添加到funcs列表中的所有三个闭包"看到"相同的变量v,(当我们在第二个循环中应用它们时)对于所有这些变量具有值120.
我试图将示例代码翻译为Scala:
import collection.mutable.Buffer
val values = List(100, 110, 120)
val funcs = Buffer[() => Int]()
for(v <- values) funcs += (() => v)
funcs foreach ( f => println(f()) )
// prints 100 110 120
// so Scala can close on the loop variable with no issue, or can it?
Run Code Online (Sandbox Code Playgroud)
Scala确实没有遇到同样的问题,或者我只是严重翻译了Eric Lippert的代码并且无法重现它?
这种行为使许多勇敢的C#开发人员绊倒了,所以我想确保Scala没有类似的奇怪问题.但是,一旦你理解了为什么C#的行为方式,Eric Lippert的示例代码的输出是有意义的(这就是闭包的工作方式,基本上):那么Scala做什么不同呢?
Scala没有相同的问题,因为v它不是var,它是一个val.因此,当你写
() => v
Run Code Online (Sandbox Code Playgroud)
编译器理解它应该生成一个返回该静态值的函数.
如果您使用a var,则可能会遇到同样的问题.但是更明确的是这是被要求的行为,因为你明确地创建了一个var,然后让函数返回它:
val values = Array(100, 110, 120)
val funcs = collection.mutable.Buffer[() => Int]()
var value = 0
var i = 0
while (i < values.length) {
value = values(i)
funcs += (() => value)
i += 1
}
funcs foreach (f => println(f()))
Run Code Online (Sandbox Code Playgroud)
(注意,如果你尝试,funcs += (() => values(i))你会得到一个越界异常,因为你已经关闭了变量i,当你打电话时,现在是3!)
C#示例的近似等价物是while循环和a var.它的行为与C#相同.
另一方面,for(v <- values) funcs += (() => v)被翻译成 values.foreach(v => funcs += () => v)
只是为了给出名字,这可能是
def iteration(v: Int) = {funcs += () => v)
values.foreach(iteration)
Run Code Online (Sandbox Code Playgroud)
闭包() => v出现在迭代体中,它捕获的不是所有迭代共享的var,而是迭代调用的参数,它不是共享的,而且是一个常量值而不是变量.这可以防止不直观的行为.
实现中可能存在变量foreach,但它不是闭包所看到的.
如果在C#中,您以单独的方法移动循环体,则会获得相同的效果.