ruby lambda capture:一种奇怪的效果和解决方法

时间:2016-10-05 19:27:09

标签: ruby lambda closures capture

closures = []
vals = ('a'..'z').to_a
until vals.empty?
 val = vals.shift()
 closures << lambda { puts val }
end
closures.each { |l| l.call() }

这个Ruby代码打印&#39; z&#39;每次通话都有点令人惊讶

def closure(val)
 lambda {puts val}
end

closures = []
vals = ('a'..'z').to_a
until vals.empty?
 val = vals.shift()
 closures << closure(val)
end
closures.each { |l| l.call() }

这打印&#39; a&#39;到&#39; z&#39;正如预期的那样。

所以我在这里看到的是Ruby lambdas在创建时捕获参数的某些不当行为 可以通过引用Ruby规范来解释这个效果吗? (我的Ruby是2.2.5p319 / Cygwin)
这应该被报告为Ruby bug跟踪器中的错误吗? 或者这是预期的行为?
或者它已经在Ruby的另一个版本中得到修复?

提前感谢您的回复

更新。这是移植到Perl的相同代码。令人惊讶的是,它按预期工作:

use strict;
use warnings;

my @vals = 'a'..'z';
my @subs = (); 

while (@vals) { 
    my $val = shift @vals; 
    push @subs, sub { print "$val\n"; }; 
} 

$_->() for @subs;

2 个答案:

答案 0 :(得分:1)

我相信这里发生的事情是第一种情况

closures = []
vals = ('a'..'z').to_a
until vals.empty?
 val = vals.shift()
 closures << lambda { puts val }
end
closures.each { |l| l.call() }

每次将lambda { puts val }推送到closures时,您只是推送一种不记得val的当前值的方法。因此,如果我们在puts val循环until的末尾添加行val = 'z',那么当您在闭包中调用每个lambda时,您正在调用puts valval的当前值。

在第二种情况下,

def closure(val)
 lambda {puts val}
end

closures = []
vals = ('a'..'z').to_a
until vals.empty?
 val = vals.shift()
 closures << closure(val)
end
closures.each { |l| l.call() }

当您将closure(val)推入闭包时,ruby能够记住参数的当前值,因此您正在推送closure('a')closure('b')等。现在,当您调用每个时l中的closures,您可以打印出一个到z。

答案 1 :(得分:1)

变量是通过引用在Ruby中捕获的,而不是通过值捕获的(在Python,JavaScript和许多其他语言中也是如此)。此外,val的范围是函数范围,而不是作用于循环内部,因此在循环的每次迭代中都不会得到新的变量val - 它&# 39; s变量val;你只是在每次迭代中为它分配另一个值。

在循环的每次迭代中,都会创建一个引用变量val的闭包 - 完全相同的变量val。因此,当稍后评估闭包时,它们都读取相同的值 - 此时(单个)变量val的值。

当你将它传递给方法并在方法中创建闭包时,它会有所不同,因为闭包捕获的变量是方法val主体中的closure,作用于该方法。每次调用方法closure时,都会得到一个新变量val,其值是传入的值,此后它永远不会被更改(closure中没有任何内容赋值给它)。因此,当稍后闭包读取该值时,它仍然是创建闭包时传递给closure的值的值。