トップ «前の日記(2006年08月08日) 最新 次の日記(2006年08月13日)» 編集

ohai日誌

2003|12|
2004|01|02|03|04|05|06|07|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|06|08|10|11|
2008|01|02|03|04|05|07|09|
2009|01|02|
2013|06|12|
2014|01|02|03|04|06|09|10|12|
2015|04|
2016|09|
2018|02|

2006年08月12日

_ instance_eval, module_eval

ここでは ブロックを与えた場合の instance_eval と module_eval の挙動について話をします。直接の応用例という話は やりにくいのですが、ここで話をするコンテキストの概念を 理解することでより高度なRubyメタプログラミングが可能になります。

参考文献として 青木さんの Ruby Hacking guideの14章コンテキストの所を挙げておきます。この話は Ruby の実装を直接 見たほうがわかりやすい人もいると思います。

コンテキストとは

まず、コンテキストとは何かを簡単に説明しましょう。 プログラムではローカル変数/インスタンス変数/定数は同じ識別子のものが 複数ある可能性があります。ローカル変数はメソッド呼びだしごとに 必要なメモリ領域が用意されます。つまりプログラムの字面上は同じ ローカル変数の参照/代入も実は異なるものを取り扱っているという事態が発生 します。またインスタンス変数はインスタンスごとにそのメモリ領域が確保 されます。定数はクラス(オブジェクト)ごとなのでまあインスタンス変数と 似ていますね。 このように、プログラムのある部分でローカル変数/インスタンス変数/定数 を参照した場合、それは実際には「どの」変数なのかを解決する必要があります。 これを解決するための情報がコンテキストです。また、似たこととして、 「self」と書いた場合、それは実際にはどのオブジェクトを指すのか、 レシーバを省略したメソッド呼びだしは実際にはどのオブジェクトのメソッドを 呼びだすのか、といった問題があります。 また、あるメソッドが終了した際にどこに戻ればよいのか、という のもプログラムに直接書かれているわけでばありませんので、これを 解決する情報が必要です。これらもコンテキストに含めます。

つまり、コンテキストというのは、プログラム上における 「暗黙の情報」をあらわしていると言えます。

Rubyには

  • メソッド呼びだしコンテキスト
  • ローカル変数コンテキスト
  • インスタンスコンテキスト
  • クラスコンテキスト

などがあります。 これは、上の文献の

  • ruby_frame
  • ruby_scope, ruby_dyna_vars
  • rb_evalの第一引数
  • ruby_class

に対応しています。 で、ここで解説するのは、インスタンスコンテキストとクラスコンテキスト の2つについてです。ちなみにこれらの用語は私が勝手に作ったものです。

インスタンスコンテキスト

これは、結局は、 「self」というキーワードがどのオブジェクトを 指しているのかという情報です。 これと関連して、

  • インスタンス変数を参照した場合どのオブジェクトのインスタンス変数が 参照されるか
  • レシーバを省略したメソッド呼出しは実際にはどのオブジェクトのメソッドを 呼びだすのか

もこれによって解決されます。

クラスコンテキスト

これは、以下の2つの問題を解決するための情報です。

  • 定数の実体を探す
  • def foo(x, y); ..... end と書いた場合にどのクラス/モジュールにこのメソッドが 定義されるかという問題

Rubyの文法要素をコンテキストの概念で説明する

これらの概念を使うことで、Rubyの文法をすっきりと説明することができます。

クラス定義(class Foo .... )の場合、

  • ローカル変数コンテキストは新たに作られる
  • インスタンスコンテキストは新しいクラスのクラスオブジェクトを指す
  • クラスコンテキストは新しいクラスのクラスオブジェクトを指す

となります。クラスコンテキストは新しいクラスのクラスオブジェクトを指す ことによって、クラスにメソッドを定義できるようになるわけです。 モジュール定義の場合も同様です。

メソッド呼び出しの場合は、

  • ローカル変数コンテキストは新たに作られる
  • インスタンスコンテキストはレシーバを指す
  • クラスコンテキストはそのメソッドの属するクラスを指す

となります。つまり、

class A
  def foo
    p "foo"
    def bar
      p "bar"
    end
  end
end

とした場合、まず、クラスAにはfooが定義され、barはどこにも定義されません。 つまり、A.new.barとすると undefined method … というエラーがでます。 ここで、

A.new.foo

とすると、"foo"と表示されるわけですが、この結果、def bar .. end という文も実行されます。ここで クラスコンテキストはそのメソッドの属するクラス、つまりAを指しているわけですから、 結果Aにbarというメソッドが定義され、A.new.barとすると"bar"が表示されます。

module_eval, instance_eval

そして、この2つのメソッドはつまりブロック内の 外側と内側でインスタンスコンテキストとクラスコンテキストを切り替えるため に存在するというわけです。

Module#module_evalは インスタンスコンテキストとクラスコンテキストを両方 レシーバオブジェクトに切り替えます。

Object#instance_evalは インスタンスコンテキストをレシーバオブジェクトに、 クラスコンテキストをレシーバオブジェクトの特異クラスオブジェクトに、 それぞれ切り替えます。

つまり、

class B
end

としたとき、

B.module_eval do
  def foo; p "foo"; end
end

とすると、fooはBのインスタンスメソッドになり。

B.instance_eval do
  def bar; p "bar"; end
end

とすると、fooはBのクラスメソッドになります。

簡単な応用例

instance_eval の簡単な応用として、プライベートメソッドを外から呼びだす ことができます。

a.instance_eval{ foo(1, x, y) }

などと書けば、fooがプライベートメソッドでも呼びだすことができます。

まとめ

このように、コンテキストを意識することで、Rubyのこのあたりの謎の挙動を 理解することが可能になるわけです。

次回は *_eval で文字列を与える場合の話の予定です。

本日のツッコミ(全1件) [ツッコミを入れる]
_ とむとむ (2007年06月28日 11:41)

コンテキスト難しいですね。<br>module_evalはインスタンスメソッドで<br>instance_evalはクラスメソッドと覚えておけば良いのでしょうか?


トップ «前の日記(2006年08月08日) 最新 次の日記(2006年08月13日)» 編集