世界線航跡蔵

Mad web programmerのYuguiが技術ネタや日々のあれこれをお送りします。

2006年11月29日

Rubyの呼び出し可能オブジェクトの比較 (3) - なんかklassの話

前回はコンテキストの概念を眺めて、klassを理解することが必要だという話になったのであった。

klass

class文の中では構築しようとしているクラスに対応するClassオブジェクトがselfとなっている。それに、class文の中でのクラスメソッド定義をみると、なんとなく、「デフォルトではselfに、指定すればそのオブジェクトに」というメソッド呼び出しにおけるレシーバー解決に似ている。

class Foo
  def self.class_method_hoge
    p :hoge
  end
end

class Bar
  def Foo.class_method_huga
    p :huga
  end
  def self.class_method_huga_of_bar
    p :huga
  end
end

このことを考えるとRubyでは、メソッドはselfに定義されると考えたくなるが、そうではない。実はこれが前に名前だけ触れたklassである。klassは正式な用語ではなく、この記事では仮にそう呼ぶというだけである。予約語classと区別するため、またRuby処理系のソースを読むとコンテキストが問題になるあたりではklassという変数はこの意味であることから、仮にklassと呼ぶことにする。

Rubyレベルでは表にあわれないがRuby処理系は内部でここでいうklassを常に保持している。これは「今メソッドを定義したらどこに定義されるか」というクラスである。この存在を考えるとRuby文法の理解がすっきりする。

トップレベルでは"main"と呼ばれるある特定のObjectインスタンスがselfなのに、トップレベルでメソッドを定義するとObjectのインスタンスメソッドになる。defの中に更にdefを書くと、それらのdefを含むクラスのインスタンスメソッドになる。メソッドは「selfに対して」定義されるわけではないのだ。

私ははじめこういう性質をRubyの魔法だと思っていたが、何のことはない、要所要所で的確にklassを切り替えているだけである。トップレベルではselfはmainで、klassはObjectだ。class文に入るとselfは構築中のクラスのClassオブジェクトになり、klassも同じになる。defの中を評価するときは(メソッドを実行するときは)selfはレシーバーで、klassはレシーバーのクラスである。

勿論、プログラマのニーズを捉えてこのようにRubyを設計したまつもとさんのデザイナセンスは誰にでも真似ができるようなことではないのだけれども、コロンブスの卵と同じで一回見てしまえば何と言うことはない。

メソッド定義先をレシーバーの解決に似せているのはやはりそう錯覚させるためであろう。普通のclass文や特異メソッド定義をしている間はそう錯覚していた方が考えなければならない要素が少なくて幸せだ。普段は錯覚していた方が良いが、instance_evalやdef内defのようなものが絡んできたときにはklassの存在を知っていた方がよい。

再びinstance_eval

obj.instance_evalはobjをselfにしてブロックを評価するのであった。このとき、実はもう一点コンテキストが変更されている。obj.instance_evalはobjの特異クラスをklassにしてブロックを評価する。つまり、instance_eval内でメソッドを定義するとobjの特異メソッドになる。

そこで、別掲のプログラムによりinstance_eval内で実際にメソッドを定義してみてどのクラスに定義されているのかを判別した。対照としてself, 定数, クラス変数の参照、および定数, クラス変数の作成も行なってみた。結果は次の通り。

文字列メソッド内Procクラス内Proc
self#<Other:0x2349c>#<Other:0x2344c>#<Other:0x2344c>
CONST:other:modified:modified
@@class:other:modified:modified
メソッド作成:singleton:singleton:singleton
CONST作成:singleton:not_defined:original
@@class作成:other:original:original

相変わらず文字列の結果はわかりやすい。全部がinstance_evalが与えるコンテキストに染まっている。instance_eval内で定義したメソッドはklassに従ってotherの特異クラスに所属している。定数もotherの特異クラスに属す。クラス変数はちょっと違う。Otherクラスに所属している。これは悩んだけれども、Amazonとの通信に失敗: 4844317210を見たらクラス変数定義先を探すときにはは都合により特異クラスをスキップすると書いてあった。

次に、メソッド内で作成したProcだ。こちらはメソッド定義先はklassに従うのであった。その通りになっている。新しく作ったメソッドはotherの特異クラスに所属している。

一方、クラス変数の定義先は元のコンテキストが生きている。メソッド内でProcを作成する場合、定数に対する代入は構文エラーを生じてしまうので記述できない。実は、定数やクラス変数の解決はklassとは別の機構で、かなり表記上の構造に縛られた形でおこなわれる。クラス変数代入がOriginalに対して行われているのはそのせいだ。

そこで、代わりに使ったのがclass文の直下で作ったProcである。class文は最後に評価した式を値として返すので、class文の戻り値を使えばこういうProcを取得できる((こういう文法のせいで文法図はすごいことになる))。こちらでは定数作成が記述できて、定数作成先は元のコンテキストであることが分かる。

つまり、obj.instance_evalはselfをobjに、メソッド定義先klassをobjのけ特異クラスにするものである、と。

再びmodule_eval

で、module_evalはinstanc_evalとどう違うのか。これはクラスの中に入ったかのように振る舞うメソッドである。instance_evalと同様に別掲のプログラムを動かしてみると次のようになった。

文字列~メソッド内Proc~クラス内Proc
selfOtherOtherOther
CONST:other_class:modified:modified
@@class:other:modified:modified
メソッド作成:other:other:other
CONST作成:other:not_defined:original
@@class作成:other:original:original

メソッドの定義先はOtherになっている。mod.module_evalはselfもklassもmodにすり替えるのである。

使い分け

で、そんな微妙な違いのものをどう使い分けるのさ、という話。

ブロック付きmodule_evalはほとんど、「外側のローカル変数を参照できるclass文、ただし新規クラスを作成することはできない」である。*1クラスを弄るのが目的ならmodule_evalを使った方が違和感がないはずだ。

Classクラスのインスタンスに対してinstance_evalを呼んでもやはり、クラス定義文とは違う。普通のクラス定義文ならklassはClassクラスのインスタンスそのものになっているが、instance_eval内ではその特異オブジェクト、つまり、メタクラスになっているからだ。

class Foo; end

Foo.instance_eval do
  def hoge
    p :ok
  end
end

foo = Foo.new
foo.hoge
  # => undefined method `hoge' for #<Foo:0x25580> (NoMethodError)

その点、module_evalならクラスを再オープンしたときと同じように気軽に使えて、しかも外側のスコープのローカル変数を参照できる。

一方、特定のオブジェクトにだけ注目して操作したいならやはりinstance_evalが便利だ。メソッドを定義すればデフォルトで特異メソッドになるという性質は影響がそのオブジェクトから外に漏れないということなので嬉しい。

まぁ、Rubyではクラスもオブジェクトなので結局同じようなことをやろうと思えばできるのだが、「クラスがオブジェクトであってクラスの特異クラスがメタクラスで」とかいうことを忘れたまま操作するには「クラスにはmodule_eval、それ以外にはinstance_eval」だ。そのあたりを意識しなければならない変な操作をするときは、仕方がない、上の表を睨みながらがんばろう。

define_method

Module#define_methodはProcを受け取るとそれを元にメソッドを定義してくれるのであった。そのメソッドを呼び出せば、Proc内のコードを実行するときのselfはメソッドのレシーバになっている*2。だから、define_methodもまたself差し替え系のメソッドであると言えなくもない。

例によってテストプログラムで試してみた。

メソッド内Procクラス内Proc
caller["define_method.rb:49:in `call'", "define_method.rb:122"]["define_method.rb:49:in `call'", "define_method.rb:124"]
self#<Other:0x22c2c @instance=:other>#<Other:0x22c2c @instance=:other>
__FILE__"define_method.rb""define_method.rb"
__LINE__1498
CONST:modified:modified
$global:other:other
@@class:modified:modified
@instance:other:other
local:original:class
メソッド作成:other:other
CONST作成:not_defined:original
@@class作成:original:original
  • callerを見ると、作成されたメソッドの定義位置はdefine_methodの呼び出し位置になっている。
  • selfは差し替わっている
  • __FILE__, __LINE__はProc作成時のまま保たれているようだ。
  • local変数は元のコンテキストが生きている。前にも述べたが、define_methodによってclosureであるメソッドを作成できたのである。
  • これで変数名の衝突を回避するというネタを話したけれども、私がぽやぽやしている間にまつもとさんがclass local instance variableを実装してしまったので1.9ではこれで済むかも。
  • 新規メソッド作成を見るとklassは差し替わっていることが分かる。
  • CONST, @@class, CONST作成, クラス変数作成を見るとProc作成時の位置に従っている。やはりこれらの名前の解決はklassとは別物なのだ。

予告。

道具立てはそろった。これでようやく、私がこれらの記事を書こうと思ったきっかけのネタに入れる。長い前置きだな。

ここから先はもはや私もどういう現象が起きるのかよく分かっていないので、一回ここでpostする。


*1ちなみに、「外側のローカル変数を参照できるclass文、ただし既存のクラスをオープンすることはできない」がClass.newだ
*2Ruby 1.6では違ったが。この変更で私は1.8移行のときに結構はまった


トラックバック

http://yugui.jp/articles/558/ping

現在のところトラックバックはありません

コメント

blog comments powered by Disqus

ご案内

前の記事
次の記事

タグ一覧

過去ログ

  1. 2016年07月
  2. 2016年01月
  3. 2015年09月
  4. 2015年08月
  5. 過去ログ一覧

フィード

フィードとは

その他

Powered by "rhianolethe" the blog system