ねんがんのObject#instance_execをてにいれたぞ! 〜Object#instance_evalに引数が渡せるようになった〜

Object#instance_exec欲しいなあ - http://rubikitch.com/に移転しました

instance_execキターーーーーー(゜∀゜)ーーーーーー!!

たった今、Ruby 1.8.7-preview4がリリースされた。
念願叶ってやっとObject#instance_execがbackportされたのだ!つまり、Ruby 1.8.7でinstance_execが使えるということだ!!

instance_execとは、Object#instance_evalに引数が渡せるようになったもの。
ついでに、関数的メソッド呼び出しで使用するとLispのletみたいになる。
Ruby 1.8.6以前でも実装することはできたが、かなり大変だった。

instance_execを使うと、コードの可読性を上げることができるので使ってみよう。

RUBY_VERSION        # => "1.8.7"
RUBY_RELEASE_DATE   # => "2008-05-26"
s = Struct.new(:foo, :bar).new(6, 8)
t = "hoge"
s.instance_exec(t) do |x|
  x                 # => "hoge"
  self              # => #<struct #<Class:0xb7e0bb24> foo=6, bar=8>
  foo               # => 6
end

instance_exec(s, t) do |x, y|
  x                 # => #<struct #<Class:0xb7e0bb24> foo=6, bar=8>
  y                 # => "hoge"
end

Ruby 1.8.7で使えるようになったRuby 1.9のメソッドたち - http://rubikitch.com/に移転しました←テストケースを更新!他の使い方はこちら。

使用例:複数の似たようなメソッドを定義する Module#def_each

Jay Fieldsのブログからネタを拝借。

似たようなメソッドを定義することがある。そのときに、コピペで定義するのは面倒だし、DRY原則に反する。
こんな感じでクラス定義の中にdefine_methodを入れてもいいのだが、いささか可読性(readability)に欠ける。サンプルは信号機クラスだ。

class TrafficSignal
  [:green, :yellow, :red].each do |meth|
    define_method(meth) do
      "Signal turned #{meth}."
    end
  end
end

そこで、メソッド名をブロック引数にとるようにして、ブロック内容をメソッド本体にする Module#def_each を定義する。
「黒魔術を具象クラスの外に追い出す」ことで、可読性は格段と上がる。

class Module
  def def_each(*methods, &block)
    methods.each do |meth|
      define_method(meth) do
        instance_exec(meth, &block)
      end
    end
  end
end

class TrafficSignal
  def_each :green, :yellow, :red do |c|
    "Signal turned #{c}."
  end
end

ts = TrafficSignal.new
ts.green                        # => "Signal turned green."
ts.yellow                       # => "Signal turned yellow."
ts.red                          # => "Signal turned red."

日本語版はこんなの。

class JapaneseTrafficSignal
  en_ja_table = {:green => "青", :yellow => "黄", :red => "赤"}
  def_each :green, :yellow, :red do |c|
    "#{en_ja_table[c]}信号"
  end
end

ts = JapaneseTrafficSignal.new
ts.green                        # => "青信号"
ts.yellow                       # => "黄信号"
ts.red                          # => "赤信号"

[2008/05/26]追記

なぜかModule#module_execはbackportされていないね。