【问题标题】:Closures and for loops in RubyRuby 中的闭包和 for 循环
【发布时间】:2010-05-14 02:16:25
【问题描述】:

我对 Ruby 有点陌生,一些闭包逻辑让我感到困惑。考虑这段代码:

array = []
for i in (1..5)
  array << lambda {i}
end
array.map{|f| f.call} # => [5, 5, 5, 5, 5]

这对我来说很有意义,因为 i 被绑定在循环之外,因此每次循环都会捕获相同的变量。对我来说,使用 each 块可以解决这个问题也很有意义:

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 中没有那么多。到底发生了什么?

编辑:请参阅答案中的 cmets;问题似乎是 j 仍在循环之外的范围内。循环中的作用域如何真正起作用?

编辑:我想我还是不明白; if 循环不创建新的范围,为什么这样:

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

【问题讨论】:

  • puts j,j 尚未分配给任何东西。局部变量需要先分配才能使用它们(它们不会自动激活。)只是 for 循环没有在其中创建新的范围,所以 j 永远不会在它的末尾超出范围。跨度>

标签: ruby closures


【解决方案1】:

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

当然,原因是我在 Ruby 中不使用 for 循环,其他人也没有,所以这对我来说真的没关系 :-)

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

§11.4.1.2.3 for 表达式

语法

  • for-expression for for-variable in expression em> do-clause end
  • for-variable left-hand-side | multiple-left-hand-side时间>

for-expression表达式不能是jump-expression

语义

for-expression 的求值方式如下:

  1. 评估表达式。让O 成为结果值。
  2. E 成为 primary-method-invocation 形式的 primary-expression [这里没有行终止符].each do | block-formal-argument-list | block-body end,其中 primary-expression 的值为 Oblock-formal-argument-listfor-variable,block-body 是 do-clausecompound-statement

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

  3. for-expression 的值是调用的结果值。

好的,所以基本上这意味着

for for_variable in expression
  do_clause
end

被翻译成

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

或者,在你的情况下:

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:它有自己的动态执行上下文,但没有自己的词法范围。

我必须承认,我仍然不确定我是否完全理解它,但没有比这更精确的了。

【讨论】:

    【解决方案2】:

    您在哪个版本的 Ruby 上运行它? 1.8 没有局部变量的块作用域,所以即使在for 结束之后,j 仍然存在(并且等于 5)。

    【讨论】:

    • 1.9.1。在探索这一点时,我会在j=i 上方放置一个puts j 并得到一个错误,所以我排除了j 的范围作为问题。但我只是在最后尝试了puts j,它确实打印了 5,所以上面有东西。所以也许真正的问题是,j 的作用域在这里是如何工作的?
    • 呃,我的意思是puts j if i &gt; 1,否则无论如何我都会收到错误
    猜你喜欢
    • 2018-05-02
    • 2011-01-12
    • 1970-01-01
    • 2012-12-15
    • 1970-01-01
    • 2016-01-02
    • 2021-10-27
    • 1970-01-01
    • 2014-05-30
    相关资源
    最近更新 更多