obj.instance_eval(&block)とblock.call(obj)の違いを確認

はてなブックマークに追加はてなブックマーク Yahoo!ブックマークに登録 ニフティクリップに追加 Livedoor クリップに追加 BuzzurlにブックマークBuzzurlにブックマーク Twitterに投稿  

=追記(2010/07/23)
Rubyベストプラクティスのひとり読書。理解が難しかった部分を中心にまとめています。
書籍から引用しすぎと思われたページ、理解したページなどを削除して、当初より公開ページを減らしました。すいません。
読書のまとめ記事は難しいと感じたので、今後はブログで読書のまとめは書かないでおきます。差し障りがあれば現存するページも削除致しますので予めご了承下さい。
=追記ここまで


p70~p71にかけて、instance_evalとProc#callの使いかたの違いがよく分からなかったので、コードを書いて実験してみました。特に、

block.arity < 1 ? pdf.instance_eval(&block) : block.call(pdf)

という部分が分かりにくかった。
Proc#arity はブロック(block)に与えられたブロック引数の数を返す。


Procオブジェクトについて

実際のコードの前にProcオブジェクトについて少々。

クラス/メソッドの定義 - Rubyリファレンスマニュアルを見ると、

最後の仮引数の直前に & があるとこのメソッドに与えられているブロッ クが手続きオブジェクト(Proc)としてこの引数に格納されます。これは、 イテレータを定義する方法の一つです。(イテレータを 定義する代表的な方法は yield を呼び出すことです。 他に Proc.new/proc を使う方法などもありま す。) ブロックが与えられなかった場合のブロック引数の値はnilです。

とありますので、def hoge(&block); fuga; end みたいなメソッドを定義したたらblockにはProcオブジェクトが入ることになる。


obj.instance_eval(&block)とblock.call(obj)の違いを確認するコード

では、実験用のコード。

 
class Introduction
  def self.output_1(&block)
    intro = Introduction.new
    block.call(intro)
  end
 
  def self.output_2(&block)
    intro = Introduction.new
    intro.instance_eval(&block)
  end
 
  def speech
    print "My name is ... "
  end
end
 
class Member
  def initialize
    @name = "Jotaro"
  end
 
  def name
    @name
  end
 
  # 実行可能
  def his_name_1
    Introduction.output_1 do |intro|
      intro.speech
      print "#{name}\n"
    end
  end
 
  # undefined local variable or method `speech'
  def his_name_2
    Introduction.output_1 do
      speech  # レシーバとなるブロック引数なしだとエラー
      print "#{name}\n"
    end
  end
 
  # undefined local variable or method `name'
  def his_name_3
    Introduction.output_2 do |intro|
      intro.speech
      print "#{name}\n"
    end
  end
 
  # undefined local variable or method `name'
  def his_name_4
    Introduction.output_2 do
      speech  # レシーバとなるブロック引数なしでも動く
      print "#{name}\n"
    end
  end
 
end
 
case ARGV[0].to_i
when 1
  Member.new.his_name_1
when 2
  Member.new.his_name_2
when 3
  Member.new.his_name_3
when 4
  Member.new.his_name_4
else
  puts '"warn: set arg 1 or 2 or 3 or 4"'
end


実行結果

引数で、1~4を与えることで実行するメソッドを変更する。

>ruby 71.rb 1
My name is ... Jotaro
 
>ruby 71.rb 2
71.rb:39:in `his_name_2': undefined local variable or method `speech' for #<Member:0x483efb0 @name="Jotaro"> (NameError)
        from 71.rb:6:in `call'
        from 71.rb:6:in `output_1'
        from 71.rb:38:in `his_name_2'
        from 71.rb:66
 
>ruby 71.rb 3
My name is ... 71.rb:48:in `his_name_3': undefined local variable or method `name' for #<Introduction:0x283ee54> (NameError)
        from 71.rb:11:in `instance_eval'
        from 71.rb:11:in `output_2'
        from 71.rb:46:in `his_name_3'
        from 71.rb:68
 
>ruby 71.rb 4
My name is ... 71.rb:56:in `his_name_4': undefined local variable or method `name' for #<Introduction:0x283ee54> (NameError)
        from 71.rb:11:in `instance_eval'
        from 71.rb:11:in `output_2'
        from 71.rb:54:in `his_name_4'
        from 71.rb:70

コードにコメントしたエラーが出ている。
obj.instance_eval(&block) の場合は、objのコンテキストで評価されるため、囲まれたスコープにあるローカル変数以外(nameメソッド)にはアクセス出来ません。
ブロック引数で渡したレシーバを明示的に書かなきゃいけない手間はありますが、Proc#callだけでいいような気もするのだけど気のせいかな・・・


日時: 2010年06月24日 17:13
コメントを投稿






トラックバック

■この記事のトラックバックURL:
http://www.mapee.jp/mpe334/mt-tb.cgi/532

この記事にトラックバックされる方は、参照先が分かるようにするために、「obj.instance_eval(&block)とblock.call(obj)の違いを確認」へのリンクをお願いいたします。
以下のHTMLタグをトラックバック送信元ページ内に挿入して下さい。



※この記事へのリンクがない、また関連のないページからのトラックバックは反映されませんので、ご了承下さい。






あわせて読みたいブログパーツ
フィードメーター - Ruby勉強ルーム