Ruby 中的闭包和 for 循环

发布于 2024-09-01 15:56:59 字数 915 浏览 4 评论 0原文

我对 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

南薇 2024-09-08 15:56:59

好吧,这越来越荒谬了。每次我尝试回答有关 Ruby 中的 for 循环如何工作的问题时,我都会答错。

当然,原因是我在 Ruby 中不使用 for 循环,其他人也不使用,所以这对我来说并不重要:-)

无论如何,解决这个问题一劳永逸地,我直接找到了最终来源,2009 年 12 月 1 日的初步草案IPA Ruby 语言规范(注定将成为 ISO Ruby 语言规范):

§11.4.1.2.3 for 表达式

语法

  • for 表达式 for for 变量 in 表达式 do-clause end
  • for-variable 左侧 | 多个左侧-侧面

for 表达式表达式不得为跳转表达式

语义

for 表达式 的计算如下:

  1. 计算表达式。令 O 为结果值。
  2. Eprimary-expression 形式的 primary-method-inspiration [无行终止符这里].each do | 块形式参数列表 | 块体 < kbd>end,其中primary-expression的值为Oblock-formal-argument-listfor-variable块体do-clause复合语句

    评估 E,但跳过第 11.2.2 节的步骤 c。

  3. for-表达式的值是调用的结果值。

好的,基本上这意味着

for for_variable in expression
  do_clause
end

被翻译成

O = expression
O.each do |for_variable|
  do_clause
end

Or,在你的情况下:

for i in 1..5
  puts j if i > 1 #undefined local variable or method `j' (NameError)
  j = i
end

被翻译成

(1..5).each do |i|
  puts j if i > 1 #no excpetion here, works just fine ??!!??
  j = i
end

啊哈!但我们忘记了一些事情!有一个不祥的“跳过第 11.2.2 节的步骤 c”。事物!那么,它说了什么?

  • 将一组空的局部变量绑定推送到 ⟦local-variable-bindings⟧。

注意步骤b

  • 将执行上下文设置为 Eb

没有被跳过。

因此,据我所知,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):

§11.4.1.2.3 The for expression

Syntax

  • for-expression for for-variable in expression do-clause end
  • for-variable left-hand-side | multiple-left-hand-side

The expression of a for-expression shall not be a jump-expression.

Semantics

A for-expression is evaluated as follows:

  1. Evaluate the expression. Let O be the resulting value.
  2. Let E be the primary-method-invocation of the form primary-expression [no line-terminator here].each do | block-formal-argument-list | block-body end, where the value of the primary-expression is O,the block-formal-argument-list is the for-variable, the block-body is the compound-statement of the do-clause.

    Evaluate E, but skip Step c of §11.2.2.

  3. The value of the for-expression is the resulting value of the invocation.

Okay, so basically this means that

for for_variable in expression
  do_clause
end

gets translated to

O = expression
O.each do |for_variable|
  do_clause
end

Or, in your case:

for i in 1..5
  puts j if i > 1 #undefined local variable or method `j' (NameError)
  j = i
end

gets translated to

(1..5).each do |i|
  puts j if i > 1 #no excpetion here, works just fine ??!!??
  j = i
end

Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does it say?

  • Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.

Note that Step b

  • Set the execution context to Eb.

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.

野鹿林 2024-09-08 15:56:59

您在哪个版本的 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文