【问题标题】:ruby lambda capture : a weird effect and workaroundruby lambda 捕获:一种奇怪的效果和解决方法
【发布时间】:2017-02-14 09:07:56
【问题描述】:
closures = []
vals = ('a'..'z').to_a
until vals.empty?
 val = vals.shift()
 closures << lambda { puts val }
end
closures.each { |l| l.call() }

这段 Ruby 代码每次调用都会打印“z”,这有点令人惊讶

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() }

这会按预期将“a”打印到“z”。

所以我在这里看到的是 Ruby lambda 在创建时捕获其参数的某些不当行为
可以请任何人通过引用 Ruby 规范来解释这种效果吗? (我的 Ruby 是 2.2.5p319/Cygwin)
这是否应该在 Ruby 错误跟踪器中报告为错误?
或者这是预期的行为?
还是已经在其他版本的 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;

【问题讨论】:

  • 出于同样的原因,同样的事情发生在 JavaScript 中,只是val 被评估的时间问题。尝试v = 6; f = -&gt; { puts v }; f.call; v = 11; f.call 获取另一个插图。
  • val 是一个引用,闭包会记住它们所在的范围,但不会创建它的快照。这将是一个坏主意记忆明智
  • 在 Perl 中:` use strict;使用警告;我的@vals = 'a'..'z';我的@subs = (); while (@vals) { 我的 $val = shift @vals;推@subs, sub { print "$val\n"; }; @subs 的 $_->(); ` 按预期从 'a' 打印到 'z'
    这怎么解释?
  • 最终通过使用循环完成了这项工作:`closures = []; vals = ('a'..'z').to_a ;如果 vals.empty,循环会中断吗? ; val = vals.shift() ;闭包
  • 太奇怪了:)

标签: ruby lambda closures capture


【解决方案1】:

在 Ruby 中,变量是通过引用而不是值来捕获的(在 Python、JavaScript 和许多其他语言中也是如此)。此外,val 的作用域是函数作用域,而不是循环内部的作用域,因此您不会在循环的每次迭代中得到一个新变量val——它是同一个变量val;您只是在每次迭代中为其分配另一个值。

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

当你将它传递给一个方法并在方法内部创建闭包时,情况就不同了,因为闭包捕获的变量是方法体中的val closure,作用域为该方法。每次调用closure 方法时,都会得到一个新变量val,它的值是传入的值,并且此后永远不会更改(closure 中没有任何内容分配给它)。所以后面这个值被闭包读取的时候,仍然是闭包创建时传入closure调用的值。

【讨论】:

    【解决方案2】:

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

    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 的当前值的方法。因此,如果我们在until 循环的末尾添加puts val 行,val = 'z',那么当您调用闭包中的每个 lambda 时,您正在调用puts val,当前值为val

    第二种情况,

    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') 等。现在,当您在@987654335 中调用每个l 时@,您可以打印出 a 到 z。

    【讨论】:

      猜你喜欢
      • 2013-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-28
      相关资源
      最近更新 更多