Hatena::Groupcoders

ラシウラ出張所 このページをアンテナに追加 RSSフィード

2007/04/12

[] ローカルコンテキスト持ちメソッド定義  ローカルコンテキスト持ちメソッド定義 - ラシウラ出張所 を含むブックマーク はてなブックマーク -  ローカルコンテキスト持ちメソッド定義 - ラシウラ出張所  ローカルコンテキスト持ちメソッド定義 - ラシウラ出張所 のブックマークコメント

rubyでは、defでメソッド定義した場合、defの外側にある変数def内では使えません。

family = "Yamada"

def hello(name)
  p "Hello " + name + " " + family
end

hello("taro") #=> familyに対し、NameErrorが出る

しかし、Module.define_methodを使うことで、ローカルコンテキストをバインドしたブロックを本体にしたメソッドの定義が可能になります。ただし、define_methodはprivateなので、instance_evalで呼び出す必要があります。

family = "Yamada"

self.class.instance_eval do
  define_method(:hello) do |name|
    p "Hello " + name + " " + family
  end
end

hello("Taro") #=> Hello Taro Yamada
family = "Tanaka"
hello("Jiro") #=> Hello Jiro Tanaka

この場合、mainオブジェクトののclassであるObjectにメソッドを追加してしまいます。

...
p Object.instance_methods.include? "hello" #=> true

このObjectの汚染を防ぐには、まず新たなModuleにメソッドを追加し、mainをそれでextendしてしまうのがいいでしょう。

family = "Yamada"

mod = Module.new
mod.instance_eval do
  define_method(:hello) do |name|
    p "Hello " + name + " " + family
  end
end
extend mod

hello("Taro") #=> Hello Taro Yamada
family = "Tanaka"
hello("Jiro") #=> Hello Jiro Tanaka
p Object.instance_methods.include? "hello" #=> false

この手法はどのobjectに対しても使用可能です。

この定義方法では、二段階のブロックを持ちます。instance_evalのdoとdefine_methodのdoです。

つまり、定義したメソッド内のコードの実行では、名前を以下の順に探していくことになります:

直上のブロックのみのコンテキストを保持するには、定義処理をメソッド化させてやればよいでしょう。

family = "Yamada"

def def_method(obj, name, &block)
  mod = Module.new
  mod.instance_eval do
    define_method(name, &block)
  end
  obj.extend mod
end

def_method(self, :hello) do |name|
  p "Hello " + name + " " + family
end

hello("Taro") #=> Hello Taro Yamada
family = "Tanaka"
hello("Jiro") #=> Hello Jiro Tanaka
p Object.instance_methods.include? "hello" #=> false