はじめに
Binding クラス「class Binding (Ruby 2.1.0 リファレンスマニュアル)」には以下のような要約が記述されています。
ローカル変数のテーブルと self、モジュールのネストなどの情報を保 持するオブジェクトのクラスです。
分かったこと・調べたことをメモしました。
クロージャ
クロージャを例に Binding を考えていきます。
以下は Ruby におけるクロージャの例です。
a = 10
p [1,2,3].map {|n| n * a } #=> [10, 20, 30]
ここで、クロージャが以下の3つから成ると考えます。
- 包んでいるもの(クロージャ)。上の例ではブロック「
{|n| n * a}
」 - 包まれているもの(変数)。上の例では変数「
a
」 - 包まれているものがあるところ(環境)。上の例では「変数
a
が定義されているスコープ(明示されてないがここでは main)」
(イメージ図)
ブロック、変数、スコープとも字義的な面での説明です。
オブジェクト指向の Ruby 世界では、プログラムにあるものはオブジェクトです。
ブロック(クロージャ)は Proc オブジェクトになります。
そして、スコープ(環境)が Binding オブジェクトになります。
変数は Binding オブジェクトに保持されます。
変数
Binding オブジェクトに保持された変数が評価されるのは、クロージャの実行時です。
以下の例で、もしも、変数a
がクロージャ定義時に評価されたとしたら、最後に「20
」が出力されるはずですが、そうはなりません。
a = 10 # main に変数`a`を定義する。
f1 = -> { a * 2 } # クロージャは変数`a`を包む。`a`の参照するオブジェクト「`10`」を包むのではない
a = 100 # 変数`a`の参照するオブジェクトを変更
p f1.() #=> 200 # クロージャ実行時に`a`が参照するオブジェクト「`100`」を評価
以下の例では、ブロック内の変数b
と main の変数b
は別物であり、クロージャ実行時にエラーになります。
クロージャが包むのは、クロージャ定義時に定義済みのブロック外の変数です。
f2 = -> { b * 2 } # 変数`b`はブロック内の変数
b = 1000 # main に変数`b`を定義する。しかし、ブロック内の変数`b`とは別物。
# さらに、クロージャ定義時にはこの変数は定義されてないので、クロージャに包まれていない。
p f2.() # NameError: undefined local variable or method `b' for main:Object 。。。(;_;)
(イメージ図)
まとめると、クロージャは、
- 「クロージャ定義時」に定義されているブロック外変数を包む
- その変数が評価されるのは、「クロージャの実行時」
です。
クロージャ定義時に変数を包んでおいて実行時に評価したいが、変数自体はオブジェクトではありません。
そこで、スコープ(環境)をオブジェクト化してそこに変数を保持した。そのオブジェクトが Binding と考えられます。
高階関数
Ruby には関数はありません。なので、高階関数もありません。
。。。という細かい話は置いといて、ここでは、feeling 的に「高階関数」と呼んでおきます。
高階関数の例です。
g = -> { a = 10 ; -> { a * 2 } } # g は関数 (-> { a * 2 }) を返す高階関数
f = g.()
f.() #=> 20
包まれている変数のスコープ(環境)は g のローカルスコープです。
それ以外は、前述の例と同様です。
高階関数定番(?)の例です。
ここではカウンタは Array にしています。カウントアップ毎に :x
が増えます。
g = -> { count = [] ; -> { count << :x } }
f1 = g.() # f1 のコール f2 のコール
f1.() #=> [:x] # 1回目
f1.() #=> [:x, :x] # 2回目
f1.() #=> [:x, :x, :x] # 3回目
f2 = g.()
f2.() #=> [:x] # 1回目
f2.() #=> [:x, :x] # 2回目
f1.() #=> [:x, :x, :x, :x] # 4回目
(イメージ図)
クロージャが包むのは定義時の(変数を含む)環境です。(上では g のスコープ)
f1 と f2 が作られる(クロージャ定義される)時、それぞれ g が実行されます。
その度に ローカル変数 count に Array オブジェクトが作られるので、f1 と f2 が包んでいる count(が参照するArrayオブジェクト)は別物になります。
binding
Binging オブジェクトは Kernel.#binding で取り出せます。
高階関数の例の f1 の Bindig オブジェクトを取り出してみます。
b1 = f1.binging
p b1.class #=> Binding
Binding#local_variable_get, Binding#local_variable_set でローカル変数の取得、設定ができます。
p b1.local_variable_get(:count) #=> [:x, :x, :x, :x]
b1.local_variable_set(:count, []) # カウンタをリセットしてみる
p f1.() #=> [:x]
p f1.() #=> [:x, :x]
[]
、[]=
などにエイリアスすると使いやすいかもしれません。
class Binding
alias [] local_variable_get
alias []= local_variable_set
end
g = -> { a = 0 ; -> { a += 1 } }
f = g.()
f.() #=> 1
f.() #=> 2
f.() #=> 3
b = f.binding
p b[:a] #=> 3
b[:a] = 10 # 変数`a` の値を 10 にする
f.() #=> 11
f.() #=> 12
なお、Binding#local_variable_get で取得できる変数はクロージャが包んだ変数です。
クロージャのブロック内(実行時スコープ)の変数は取得できません。
g = -> { a = 10; -> { x = 2; a * 2 } }
f = g.()
b = f.binding
b.local_variable_get :a #=> 10
b.local_variable_get :x # エラー (NameError: local variable `x' not defined for #<Binding:...)
Proc 以外のオブジェクトも Binding をもっています。
ただし、binding メソッドは private になっていることが多いようです。
obj = Object.new
b = obj.send(:binding) # private メソッドだが、send でなら呼び出せる
p b.class #=> Binding
補足
本稿で使ってきた言葉ですが、
-「クロージャ定義時に見えているスコープ」=> 「レキシカルスコープ(lexical scope)」
-「クロージャが包んでいる変数」=>「レキシカル変数(レキシカルスコープの変数)」
-「レキシカルスコープの持ち主(上の例の main や g)」=>「エンクロージャ(enclosure)」
といったりします。
これらは Ruby に限らない用語ですが、どの程度一般的かなど詳しいまではよく分かりません。m(_ _)m
何か調べる際の参考にしてください。
余談
ブロックは、レキシカル変数を捕らえてクロージャとして機能したりしますが、実行時(ダイナミックな)スコープを利用する用法もあります。
v = 10
# instance_eval は self をレシーバにしてブロックを評価する
instance_eval do # ここでは、レシーバが self なので self を self にしている。
a = 1 # ブロック内(ダイナミックスコープ)の変数
b = 2 # ブロック内(ダイナミックスコープ)の変数
p a #=> 1
p b #=> 2
p v #=> 10 # ブロック外(レキシカルスコープ)の変数
end
p v #=> 10
p a # エラー! (NameError: undefined local variable or method `a' ...)
p b # エラー! (NameError: undefined local variable or method `b' ...)
上の変数 a
、b
は instance_eval のブロック内だけの変数です。ブロックを抜けると未定義になります。
現在の環境を一時的な変数などで汚したくない等の場合(私は irb や pry などを使っている時に、たまにあります)は、上のようなことができます。
おわりに
本稿内容の動作確認は以下の環境で行っています。
- Ruby 2.1.5 p273