lambda/Procと変数の関係(クロージャ)

クロージャ

クロージャはブロックの内部に変数を保持できる、ということはいろんなところで書いてあります。

Rubyのブロックはそれ自身をオブジェクト化することができ、そうすることによってメモリ上に独立して存在できるようになる。

 lambda { n += 1 } # => #<Proc:0x0001f57c@-:27>

ブロックをオブジェクト化したものはProcクラスのインスタンスであり、callメソッドを呼ぶことによってブロック内の手続きを呼び出すことができる。そしてこの場合でもブロックがその外側で定義されたローカル変数を参照できるという性質は保たれる。このようなブロックの性質はクロージャと呼ばれる

 n = 0
 main = lambda { n += 1 }
 main.call # => 1
 main.call # => 2
 main.call # => 3

Rubyのブロック(クロージャ)はローカル変数をインスタンス変数に変えるマジックだ!

このカウントアップの例がよく挙げられますが、だからどうなんだろう、と思ってしまったり。
そして、内部保持しているといっても外からの擾乱を受ける。

 n = 1
 main.call # => 2

lambda(Proc)宣言以前に存在する変数は取り込まれる

クロージャというか手続きlambda/Proc(以下はlambdaで統一)は、その宣言/生成の時点での環境の変数をそのlambdaの中に取り込みます。

pry(main)> a = 0
=> 0
pry(main)> b = 1
=> 1
pry(main)> l = lambda{ puts a,b }
=> #<Proc:0x007fc28224bd18>
pry(main)> l.call
0
1
=> nil
pry(main)> a = 2
=> 2
pry(main)> l.call
2
1
=> nil

aもbもlの中に取り込まれています(囲い込み)。そしてaを変更したらlに反映されます(外部が見える)。

lambda(Proc)宣言以降に出現する変数は取り込めない

で。
lambdaを生成する時点で環境に存在していない変数はどうなるのか。

pry(main)> ll = lambda{ puts c,d }
=> #<Proc:0x007fc282128bc0@(pry):43 (lambda)>
pry(main)> c = 3
=> 3
pry(main)> d = 4
=> 4
pry(main)> ll.call
NameError: undefined local variable or method `c' for main:Object
from (pry):43:in `block in __pry__'

やはりlambdaに取り込まれていません。
「授業開始時点で着席していない変数には単位あげません」
的な(なんだそりゃ)。
でも。先にlambdaを宣言しておいて、それ以降に環境に生成された変数を取り込むことは当然ながら可能ですよね。ブロック変数に入れれば良いわけです。

pry(main)> lll = lambda{ |y, z| puts y, z }
=> #<Proc:0x007fc28111d2a8>
pry(main)> w = 10
=> 10
pry(main)> x = 11
=> 11
pry(main)> lll.call(w, x)
10
11
=> nil

lambda宣言時に存在しなかった変数をローカル変数に使うとブロック内ローカル変数になる

lambda宣言時に環境に存在しなかった変数をlambdaで使っても、環境側には見えません。lambda内のローカル変数になっている。

pry(main)> a = 40
pry(main)> b = 2
pry(main)> l = lambda{ puts a,b; s = a + b; puts s }
=> #<Proc:0x007fc2810ff410@(pry):65 (lambda)>
pry(main)> l.call
40
2
42
=> nil
pry(main)> s
NameError: undefined local variable or method `s' for main:Object
from (pry):67:in `__pry__'

もちろん宣言後にlambda内のローカル変数と同名の変数を環境で使っても、当然ながら反映されず。

pry(main)> s = 100
pry(main)> l.call
40
2
42
=> nil
pry(main)> s
=> 100

lambdaとメソッドとの違い

やはり、渡す変数の明示が必要か否か、が大きく違いますよね。
メソッドは基本的にローカル変数ばかりなので、いちいち変数を渡さないといけない。
渡さないとRubyインタープリタ

undefined local variable or method

と怒ってきます。
でも、lambdaだと、それ自身がその環境を保持してくれる。
ということで、メソッド内でおなじことを書いているときには、lambdaにすることで簡単にまとめることが出来ます。
別メソッドに分離する場合には、変数を詰まないといけないので、ちょっと面倒。