Blogaomu

WEBアプリケーション開発とその周辺のメモをゆるふわに書いていきます。

BasicObject#instance_eval についてメモ

BasicObject#instance_eval で理解不足の部分があったのでメモっておきます。 Ruby 1.9.3p448 で実行しています。

こんな感じのクラスを作りました。

class Klass
  # なんらかの処理の後に実行する
  def proc_after_run(&block)
    @proc = block
  end

  def run
    # なんかの処理
    puts '--- running... ---'
    # …
    # …
    # 定義済み proc を呼び出す
    call_proc
  end

  private
  def call_proc
    env = Object.new
    unless @proc.nil?
      env.instance_eval do
        @proc.call
      end
    end
  end
end

ブロックを定義して実行してみましょう。

WnlDLj6o8L.gif

oh... エラーが起きたようです。

#=> NoMethodError: undefined method `call' for nil:NilClass
    from /Users/takayuki_atkwsk/hogehoge/klass.rb:16:in `block in call_proc'

nil に対して call メソッドを呼んでいます。あれ?@procにセットしたじゃないか。 と思い込んでいたのですが、大事なことを忘れていました。

オブジェクトのコンテキストで評価するとは評価中の self をそのオブジェクトにして実行するということです。

instance method BasicObject#instance_eval

上の例で言えば、envのコンテキストで@proc.callが評価されています。この中ではselfenvですので ブロックの中の@procenvに属しています。もちろんenvでは@procを定義していないので nil となり上記のエラーとなった、と理解しました。

instance_evalにはもう一つルールがあって、

ただし、ローカル変数だけは、文字列 expr の評価では instance_eval の外側のスコープと、ブロックの評価ではそのブロックの外側のスコープと、共有します。

instance method BasicObject#instance_eval

ということで書き換え...。

class Klass
  # 省略...

  private
  def call_proc
    env = Object.new
    unless @proc.nil?
      procboj = @proc  # <- append
      env.instance_eval do
        procobj.call   # <- modify
      end
    end
  end
end

実行してみましょう。

XthuyT9HS6.gif

期待した動作になりましたね。Rubyの理解度がちょっと増しました。