2006年08月06日
_ Module#define_method
Rubyでは通常メソッドは def を使って定義します。しかしそれ以外にもメソッドを クラスに追加する方法があります。それがdefine_methodです。 このメソッドは与えられたブロック(やMethod,Proc,UnboundMethodのインスタンス) をメソッドとして登録します。例えば以下のようにします。
define_method(:foo){|x,y| 2*x*y}
すると
foo(2,6) # => 24
というように使えます。
このメソッドを使う場合には、スコープに注意すべきです。
- ブロックの中では、基本的に定義したクラスのインスタンスのコンテキストになる。
- レシーバを省略したメソッド呼びだし
- インスタンス変数
- self
- etc
- ただし、ブロックの中から外のローカル変数が見える。
というようになっています。逆にこれがこのメソッドの利点でもあります。 define_methodを使うことで、以下のようなことができます。
- メソッド名を動的に決めることができる
- 外側のローカル変数を読み書きできる
1番目はevalを使うことで同じことができますが、2番目はdefine_methodで なければできません。
まずは、attr_accessorのようなものを作ってみましょう。 デフォルト値をあらかじめ指定できるようにしてみましょう。
class Module def attr_accsssor_default(name, val) iv_name = "@#{name}" attr_writer(name) define_method(name) do if instance_variables.include?(iv_name) instance_variable_get(iv_name) else val end end end end class A attr_accsssor_default :foo, "foo" end a = A.new p a.instance_variables # => [] p a.foo # => "foo" p a.instance_variables # => [] a.foo = 3 p a.instance_variables # => ["@foo"] p a.foo # => 3
ここでローカル変数(引数)valを読んでいます。instance_variables, instance_variable_get/setは名前そのままの機能です。
次はこれの変形版、インスタンス変数を使わないようにします。
class Module def attr_accsssor_default_(name, val) define_method("#{name}=") do |new_val| val = new_val end define_method(name) do val end end end class A attr_accsssor_default_ :bar, Math::E end a = A.new p a.bar # => 2.71828182845905 a.bar = Math::PI p a.bar # => 3.14159265358979
ここではvalを読み書き両方しています。
__send__と組合せることでForwardableもどきを作ります。
module Forwardable_ def def_delegator(accessor, *methods) methods.each do |m| define_method(m) do |*args| instance_variable_get(accessor).__send__(m, *args) end end end end class B extend Forwardable_ def_delegator :@baz, :foo, :bar def initialize(baz) @baz = baz end end
オブジェクトの特異クラスに対するdefine_method、define_singleton_methodを作ります。
class Object def define_singleton_method(name, &block) (class << self self end).module_eval{ define_method(name, &block) } end end
特異クラス構文の中でselfが特異クラスオブジェクトになることを利用します。 また、define_methodはprivate methodなのでmodule_evalでそのあたりをを ごまかします。 このようにブロックをたらいまわしにすることでクラスに直接触れずに メソッドを定義することもできます。あまり濫用するとプログラムがわかり にくくなりそうなテクニックですね。
次回はまあ情報取得の類でもしましょう。
_ プログラミング言語について
プログラミング言語について議論するとき○○ができる/できないと いう議論はそれほど意味がありませんよね。簡単にできるか/否か、 わかりやすく書けるか/書けないかが問題になるべきですよね。