LoginSignup
39
34

More than 5 years have passed since last update.

Ruby: Binding

Posted at

はじめに

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)」

(イメージ図)

closure1.png

ブロック、変数、スコープとも字義的な面での説明です。
オブジェクト指向の 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 。。。(;_;)

(イメージ図)

closure2.png

まとめると、クロージャは、

  • 「クロージャ定義時」に定義されているブロック外変数を包む
  • その変数が評価されるのは、「クロージャの実行時」

です。

クロージャ定義時に変数を包んでおいて実行時に評価したいが、変数自体はオブジェクトではありません。
そこで、スコープ(環境)をオブジェクト化してそこに変数を保持した。そのオブジェクトが 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回目

(イメージ図)

closure3.png

クロージャが包むのは定義時の(変数を含む)環境です。(上では 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' ...)

上の変数 ab は instance_eval のブロック内だけの変数です。ブロックを抜けると未定義になります。
現在の環境を一時的な変数などで汚したくない等の場合(私は irb や pry などを使っている時に、たまにあります)は、上のようなことができます。

おわりに

本稿内容の動作確認は以下の環境で行っています。

  • Ruby 2.1.5 p273
39
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
34