PR

リフレクション

 メタプログラミングの例として次にリフレクション(reflection)を取り上げましょう。英単語では「反射」とか「反省」を意味しますが,プログラミングの文脈で用いられた場合,実行中のプログラムの情報を取り出したり,変更したりする機能のことを指します。

 Rubyの場合,表1に挙げたリフレクション機能を備えています。変数やメソッドの一覧を得たり,値を取得・変更したりするさまざまな手法が用意されていることが分かります。考えてみれば,2005年7月号で解説したMix-inを行うincludeですら,Rubyの文法ではなく,メソッドによって実現されています。Rubyでは動的にプログラムを操作するという性質が徹底されているのです。

表1●Rubyの備えるリフレクション機能
表1●Rubyの備えるリフレクション機能

 では,これらの機能を使うとどのようなことができるのか,具体的な事例を見ながら考えてみましょう。

メタプログラミングの事例

 まずはリフレクションを用いた事例を紹介します。

 あるオブジェクトに対する呼び出しを別のオブジェクトに転送したい場合があるでしょう。Rubyでは委譲を行うためのライブラリとしてDelegetorが用意されています。Delegatorオブジェクトはメソッドの委譲先のオブジェクトを持ち,メソッド呼び出しを委譲先に転送します。デザイン・パターンで言うとProxyパターンの基礎部分を実現するものです。Delegatorを使うためには,SimpleDelegatorクラスを使います。

require 'delegator'
d = SimpleDelegator.new(a)

 これだけでオブジェクトdに対するメソッド呼び出しはすべてオブジェクトaに転送されます。転送されるだけではあまりうれしくないのですが,このオブジェクトに特異メソッド*1を付加して一部だけ挙動を変えるなど,さまざまな使い道があります。

 以上の処理を他の言語,例えばJavaで実現するとしたらどうなるでしょうか。静的型を持つJavaでは型を合わせるため,aの型に対応してメソッドを転送する専用のDelegator用クラスを個別に用意する必要があります。aのクラスにいくつメソッドがあるかは分かりませんが,場合によっては非常に数が多くなることも考えられます。いずれにしても,おそらくは専用のツールで自動生成でもしないことには現実的ではないでしょう。

 DelegatorはRubyの動的型をリフレクションの組み合せによって初めて実現できているといえるでしょう。

リフレクション機能を使う

 それでは,Delegatorクラスを実現するためにRubyのリフレクション機能がどのように使われているかを見てみましょう。図3はSimpleDelegatorの実装の一部です。理解のために実際のものよりもかなり単純化してあります。

class SimpleDelegator

 # (a)メソッドの未定義化
 preserved = ["__id__", "object_id", "__send__", "respond_to?"]
 instance_methods.each do |m|
 next if preserved.include?(m)
 undef_method m
 end

 # (b)オブジェクトの初期化
 def initialize(obj)
  @_sd_obj = obj
 end

 # (c)method_missing
 def method_missing(m, *args)
   unless @_sd_obj.respond_to?(m)
   super(m, *args)
  end
  @_sd_obj.__send__(m, *args)
 end

 # (d)メソッド・チェック
 def respond_to?(m)
  return true if super
  return @_sd_obj.respond_to?(m)
 end
end
図3●SimpleDelegatorの実装内容

 図3のコードは,大きく4つの部分に分かれています。まず,一番重要なところから説明しましょう。(c)で示した部分がDelegatorの心臓部です。Rubyはメソッド呼び出しを行った際に,オブジェクトがそのメソッドを知らない場合,まずmethod_missingという名前のメソッドを呼び出します。method_missingの第1引数は呼び出されていたメソッド名,残りがそのメソッドに渡されるはずだった引数です。method_missingのデフォルトの実装は例外を発生させますが,これをオーバーライドすることで未知のメソッドに対応させています。ここでは,次の2つの処理を進めています。

(1)委譲先のオブジェクトがそのメソッドを知らなかったら(respond_to?),デフォルトの実装を呼び出し(super),エラーを発生させる。

(2)そうでなければ,__send__を使って委譲先のメソッドを呼び出す。

 __send__というのはオブジェクトのメソッドを呼び出すメソッドです。このメソッドにはsendという別名がありますが,ありふれた名前でいかにも重複しそうなので__send__という名前の方を採用しています。

 SimpleDelegatorの残りの部分の実装は比較的簡単です。説明した通り,SimpleDelegatorはmethod_missingを使ってメソッドの転送を行っています。しかし,RubyのObjectクラスは比較的たくさんのメソッドを提供する「大きなクラス」です。Objectクラスは実に40ものメソッドを抱えています。SimpleDelegatorはObjectクラスのサブクラスですが,これら40もの「知っているメソッド」を転送できないのは困ります。そこで,(a)の部分で,instance_methodsで得られるメソッドの一覧を使って,必須のメソッド(__id__,object_id,__send__,respond_to?)を除いたメソッドを未定義化しています。

 (b)ではSimpleDelegatorの初期化の部分で委譲先を設定しています。(d)の部分ではrespond_to?が正しく反応するように,まずsuperを使って自分のメソッドを調べた後,委譲先のメソッドをチェックするようにしています。