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

スポンサーリンク
スポンサーリンク
ライフスタイル関連のコンテンツ
お金 | 仕事 | 勉強 | プライベート | 健康 |
プログラミング関連のコンテンツ
C言語/C++入門 | Ruby入門 | Python入門 | プログラミング全般

=追記(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だけでいいような気もするのだけど気のせいかな・・・

スポンサーリンク
 
スポンサーリンク