クロージャ¶
キャプチャされたブロックとprocリテラルは、ローカル変数とself
をクロージャします。これは、例でよりよく理解できます。
x = 0
proc = ->{ x += 1; x }
proc.call # => 1
proc.call # => 2
x # => 2
または、メソッドから返されたprocを使用します。
def counter
x = 0
->{ x += 1; x }
end
proc = counter
proc.call # => 1
proc.call # => 2
上記の例では、x
はローカル変数ですが、procリテラルによってキャプチャされました。この場合、コンパイラはx
をヒープに割り当て、procのコンテキストデータとして使用して動作させます。これは、通常、ローカル変数はスタックに存在し、メソッドが返された後に消えるためです。
クロージャ変数の型¶
コンパイラは通常、ローカル変数の型についてある程度賢明です。例えば
def foo(&)
yield
end
x = 1
foo do
x = "hello"
end
x # : Int32 | String
コンパイラは、ブロックの後、x
がInt32またはStringになる可能性があることを認識しています(メソッドは常にyieldするため、常にStringになることを認識している可能性があります。これは将来改善される可能性があります)。
ブロックの後にx
に別のものが代入された場合、コンパイラは型が変更されたことを認識します。
x = 1
foo do
x = "hello"
end
x # : Int32 | String
x = 'a'
x # : Char
ただし、x
がprocによってクロージャされた場合、型は常にそれに割り当てられたすべての型の混合型になります。
def capture(&block)
block
end
x = 1
capture { x = "hello" }
x = 'a'
x # : Int32 | String | Char
これは、キャプチャされたブロックがクラスまたはインスタンス変数に格納され、命令の間に別のスレッドで呼び出された可能性があるためです。コンパイラはこれについて徹底的な分析を行いません。変数がprocによってキャプチャされた場合、そのprocの呼び出し Zeitpunkt は不明であると想定します。
これは、procが呼び出されたり格納されたりしていないことが明らかであっても、通常のprocリテラルでも発生します。
x = 1
->{ x = "hello" }
x = 'a'
x # : Int32 | String | Char