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でそのあたりをを ごまかします。 このようにブロックをたらいまわしにすることでクラスに直接触れずに メソッドを定義することもできます。あまり濫用するとプログラムがわかり にくくなりそうなテクニックですね。
次回はまあ情報取得の類でもしましょう。
_ プログラミング言語について
プログラミング言語について議論するとき○○ができる/できないと いう議論はそれほど意味がありませんよね。簡単にできるか/否か、 わかりやすく書けるか/書けないかが問題になるべきですよね。