Ruby 中的闭包和 for 循环
我对 Ruby 有点陌生,一些闭包逻辑让我感到困惑。考虑这段代码:
array = []
for i in (1..5)
array << lambda {i}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
这对我来说是有意义的,因为 i 绑定在循环之外,因此循环的每次行程都会捕获相同的变量。对我来说,使用 every 块可以解决这个问题也是有意义的:
array = []
(1..5).each{|i| array << lambda {i}}
array.map{|f| f.call} # => [1, 2, 3, 4, 5]
...因为 i 现在每次都单独声明。但现在我迷失了:为什么我不能通过引入中间变量来修复它?
array = []
for i in 1..5
j = i
array << lambda {j}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
因为 j 每次循环都是新的,所以我认为每次循环都会捕获不同的变量。例如,这绝对是 C# 的工作方式,以及——我认为——Lisp 的 let 行为方式。但在 Ruby 中则不然。到底发生了什么?
编辑:查看答案中的评论;问题似乎是 j 仍在循环之外的范围内。循环中的范围实际上是如何工作的?
编辑:我想我还是不明白;如果循环不创建新的作用域,为什么会这样:
for i in 1..5
puts j if i > 1 #undefined local variable or method `j' for main:Object (NameError)
j = i
end
I'm kind of new to Ruby and some of the closure logic has me a confused. Consider this code:
array = []
for i in (1..5)
array << lambda {i}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
This makes sense to me because i is bound outside the loop, so the same variable is captured by each trip through the loop. It also makes sense to me that using an each block can fix this:
array = []
(1..5).each{|i| array << lambda {i}}
array.map{|f| f.call} # => [1, 2, 3, 4, 5]
...because i is now being declared separately for each time through. But now I get lost: why can't I also fix it by introducing an intermediate variable?
array = []
for i in 1..5
j = i
array << lambda {j}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]
Because j is new each time through the loop, I'd think a different variable would be captured on each pass. For example, this is definitely how C# works, and how -- I think-- Lisp behaves with a let. But in Ruby not so much. What's really happening?
Edit: See comments in the answers; the problem seems to be that j is still in scope outside the loop. How does scope in loops really work?
Edit: I guess I still don't understand; if loops don't create new scopes, why this:
for i in 1..5
puts j if i > 1 #undefined local variable or method `j' for main:Object (NameError)
j = i
end
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好吧,这越来越荒谬了。每次我尝试回答有关 Ruby 中的
for
循环如何工作的问题时,我都会答错。当然,原因是我在 Ruby 中不使用
for
循环,其他人也不使用,所以这对我来说并不重要:-)无论如何,解决这个问题一劳永逸地,我直接找到了最终来源,2009 年 12 月 1 日的初步草案IPA Ruby 语言规范(注定将成为 ISO Ruby 语言规范):
好的,基本上这意味着
被翻译成
Or,在你的情况下:
被翻译成
啊哈!但我们忘记了一些事情!有一个不祥的“跳过第 11.2.2 节的步骤 c”。事物!那么,它说了什么?
注意步骤b
没有被跳过。
因此,据我所知,
for
循环获取其自己的执行上下文,该执行上下文一开始是当前执行上下文的副本,但它没有获取其自己的执行上下文。自己的一组局部变量绑定。 IOW:它获得自己的动态执行上下文,但没有自己的词法范围。我必须承认,我仍然不确定我是否完全理解它,但没有比这更精确的了。
Okay, this is getting ridiculous. Every time I try to answer a question about how
for
loops work in Ruby, I get it wrong.The reason for this is, of course, that I don't use
for
loops in Ruby, neither does anybody else, so it really doesn't matter to me :-)Anyway, to settle the matter once and for all, I went straight to the ultimate source, the December 1, 2009 preliminary Draft of the IPA Ruby Language Specification (destined to become the ISO Ruby Language Specification):
Okay, so basically this means that
gets translated to
Or, in your case:
gets translated to
Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does it say?
Note that Step b
is not skipped.
So, as far as I can see, a
for
loop gets its own execution context, which starts out as a copy of the current execution context, but it does not get its own set of local variable bindings. IOW: it gets its own dynamic execution context, but not its own lexical scope.I must admit, I'm still not sure I fully understand it, but it doesn't get any more precise than this.
您在哪个版本的 Ruby 上运行该程序? 1.8 没有局部变量的块作用域,因此即使在
for
结束之后,j 仍然存在(并且等于 5)。What version of Ruby are you running this on? 1.8 does not have block scope for local variables, so j is still hanging around (and equal to 5) even after the end of the
for
.