2016-10-05 8 views
3
closures = [] 
vals = ('a'..'z').to_a 
until vals.empty? 
val = vals.shift() 
closures << lambda { puts val } 
end 
closures.each { |l| l.call() } 

ビットは驚くべきことであるすべての呼び出しルビーラムダキャプチャ:奇妙な効果と回避策

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

このプリント「」に「Z」のために、このRubyコードを印刷「Z」、それは次のようになりますよう期待される。

ので、私はここを参照すると、彼らの創造
の瞬間にそれらのパラメータを取得するRubyのラムダでは、特定の不正行為である、誰もがRubyの仕様を引用して、この効果を説明していただけますか? (私のRubyは2.2.5p319/Cygwinです)
これはRubyのバグトラッカーのバグとして報告されるべきですか?
これは予期された動作ですか?
、または既にRubyのいくつかのバージョンで修正されていますか?あなたの回答を事前に

おかげ

UPDATE。 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; 
+0

JavaScriptで同じ理由で同じことが起こります。これは 'val'が評価されるときの問題です。試してみましょう 'v = 6; f = - > {puts v}; f.call; v = 11; f.call'を表示します。 –

+0

'val'は参照ですが、closuresはそのスコープを覚えていますが、そのスナップショットは作成しません。 Perlでメモリが賢明であるというのは悪い考えです – Tiago

+0

: 'use strict; 警告を使用します。 my @vals = 'a' .. 'z'; my @subs =(); while(@vals){ my $ val = shift @vals; push @subs、sub {print "$ val \ n"; }; } @subsの$ _->(); ' 'a'から 'z'が期待通りに印刷されます
これはどのように説明できますか? –

答えて

1

が最初のケース

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

にあなたがclosureslambda { puts val }を押すたびに、あなたが単にない方法で推進していますvalの現在の値を覚えていない。したがって、untilループの末尾にputs valという行を追加すると、val = 'z'となるので、閉包内の各ラムダを呼び出すときには、valの現在の値でputs valを呼び出しています。あなたが呼び出すときに、今closure('a')、などを推進しているように、第2のケースで

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)を押し

、ルビーは、引数の現在の値を覚えておくことが可能ですそれぞれ lclosuresに入れると、aからzをプリントアウトすることができます。

1

変数は、値ではなくRubyで参照によって取得されます(Python、JavaScript、その他多くの言語でも同じことが適用されます)。さらに、valのスコープはループ内にスコープされていない関数スコープであるため、ループの各繰り返しで新しい変数valを取得することはありません。同じ変数valです。各反復でそれに別の値を割り当てるだけです。

ループの各繰り返しで、変数val - まったく同じ変数valを参照するクロージャが作成されます。したがって、クロージャが後で評価されるとき、それらはすべて同じ値、つまりその時点で(単一の)変数valの値を読み込みます。

メソッドにクロージャを渡してクロージャを作成すると、クロージャがキャプチャする変数がメソッドclosureの本体にあるvalであり、そのメソッドにスコープが設定されているため、クロージャのクロージャが異なります。メソッドclosureを呼び出すたびに、値が渡された値である新しい変数valが得られます。その後は変更されません(closureには何も割り当てられません)。したがって、値が後でクロージャによって読み取られるときは、クロージャが作成されたときにはまだclosureのコールに渡された値になります。

関連する問題