yieldとProc#callの違い
これらは似てるけど、引数の渡し方に違いがあるんですね。
まとめると、渡される引数の数と期待する引数の数が違うときは以下のように動きます。
- 期待している引数が0個あるいは可変長のときは、yieldもProc#callも同じ動作
- 0個と可変長の場合のarityは同じ値を返してるんですね、知らんかったっす。参照 http://www.ruby-lang.org/ja/man/html/Proc.html
- 期待している引数が1個で
- 渡される引数が0個の場合は、引数としてnilが渡される
- 渡される引数が2個以上の場合は、渡された引数群が配列として渡される
- 期待している引数が2個以上で、
- 渡される引数がそれより少ない場合
- yield は 足りない分はnil
- Proc#call は ArgumentError(wrong number of arguments actual for expected)
- 渡される引数がそれより多い場合
- yield は 余った分は渡されない
- Proc#call は ArgumentError(wrong number of arguments actual for expected)
- 渡される引数がそれより少ない場合
厳密に引数の数を合わせる必要がある場合のブロックの呼び出しはProc#callで、呼び出されるブロック側の自由を尊重する場合のブロックの呼び出しはyieldで、っていう切り分けになるんでしょうね。っていうか、今回調べてyieldが好きになりました。
以下検証コードと実行結果です。環境はruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]です。
def foo(*args) print "yield : " yield(*args) end def bar(*args, &block) print "Proc#call: " block.call(*args) end blocks = [ proc{print "proc actual N/A"}, proc{|x| print "proc actual => #{x.inspect}"}, proc{|x, y| print "proc actual => #{x.inspect}, #{y.inspect}"}, proc{|x, y, z| print "proc actual => #{x.inspect}, #{y.inspect}, #{z.inspect}"}, proc{|*args| print "proc actual => #{args.inspect}"}, lambda{print "lambda actual N/A"}, lambda{|x| print "lambda actual => #{x.inspect}"}, lambda{|x, y| print "lambda actual => #{x.inspect}, #{y.inspect}"}, lambda{|x, y, z| print "lambda actual => #{x.inspect}, #{y.inspect}, #{z.inspect}"}, lambda{|*args| print "lambda actual => #{args.inspect}"}, Proc.new{print "Proc.new actual N/A"}, Proc.new{|x| print "Proc.new actual => #{x.inspect}"}, Proc.new{|x, y| print "Proc.new actual => #{x.inspect}, #{y.inspect}"}, Proc.new{|x, y, z| print "Proc.new actual => #{x.inspect}, #{y.inspect}, #{z.inspect}"}, Proc.new{|*args| print "Proc.new actual => #{args.inspect}"} ] arguments_set = [ [], ['a'], ['a', 'b'], ['a', 'b', 'c'] ] methods = [:foo, :bar] blocks.each do |block| arguments_set.each do |arguments| methods.each do |method| next if arguments.length == block.arity print "#{arguments.length} for #{block.arity} " begin send(method, *arguments, &block) print " => OK\n" rescue print " => NG #{$!.class} #{$!.to_s}\n" end end end end
で実行結果はこちら
0 for -1 yield : proc actual N/A => OK 0 for -1 Proc#call: proc actual N/A => OK 1 for -1 yield : proc actual N/A => OK 1 for -1 Proc#call: proc actual N/A => OK 2 for -1 yield : proc actual N/A => OK 2 for -1 Proc#call: proc actual N/A => OK 3 for -1 yield : proc actual N/A => OK 3 for -1 Proc#call: proc actual N/A => OK 0 for 1 yield : (irb):337: warning: multiple values for a block parameter (0 for 1) from (irb):327 proc actual => nil => OK 0 for 1 Proc#call: (irb):337: warning: multiple values for a block parameter (0 for 1) from (irb):332 proc actual => nil => OK 2 for 1 yield : (irb):337: warning: multiple values for a block parameter (2 for 1) from (irb):327 proc actual => ["a", "b"] => OK 2 for 1 Proc#call: (irb):337: warning: multiple values for a block parameter (2 for 1) from (irb):332 proc actual => ["a", "b"] => OK 3 for 1 yield : (irb):337: warning: multiple values for a block parameter (3 for 1) from (irb):327 proc actual => ["a", "b", "c"] => OK 3 for 1 Proc#call: (irb):337: warning: multiple values for a block parameter (3 for 1) from (irb):332 proc actual => ["a", "b", "c"] => OK 0 for 2 yield : proc actual => nil, nil => OK 0 for 2 Proc#call: => NG ArgumentError wrong number of arguments (0 for 2) 1 for 2 yield : proc actual => "a", nil => OK 1 for 2 Proc#call: => NG ArgumentError wrong number of arguments (1 for 2) 3 for 2 yield : proc actual => "a", "b" => OK 3 for 2 Proc#call: => NG ArgumentError wrong number of arguments (3 for 2) 0 for 3 yield : proc actual => nil, nil, nil => OK 0 for 3 Proc#call: => NG ArgumentError wrong number of arguments (0 for 3) 1 for 3 yield : proc actual => "a", nil, nil => OK 1 for 3 Proc#call: => NG ArgumentError wrong number of arguments (1 for 3) 2 for 3 yield : proc actual => "a", "b", nil => OK 2 for 3 Proc#call: => NG ArgumentError wrong number of arguments (2 for 3) 0 for -1 yield : proc actual => [] => OK 0 for -1 Proc#call: proc actual => [] => OK 1 for -1 yield : proc actual => ["a"] => OK 1 for -1 Proc#call: proc actual => ["a"] => OK 2 for -1 yield : proc actual => ["a", "b"] => OK 2 for -1 Proc#call: proc actual => ["a", "b"] => OK 3 for -1 yield : proc actual => ["a", "b", "c"] => OK 3 for -1 Proc#call: proc actual => ["a", "b", "c"] => OK 0 for -1 yield : lambda actual N/A => OK 0 for -1 Proc#call: lambda actual N/A => OK 1 for -1 yield : lambda actual N/A => OK 1 for -1 Proc#call: lambda actual N/A => OK 2 for -1 yield : lambda actual N/A => OK 2 for -1 Proc#call: lambda actual N/A => OK 3 for -1 yield : lambda actual N/A => OK 3 for -1 Proc#call: lambda actual N/A => OK 0 for 1 yield : (irb):342: warning: multiple values for a block parameter (0 for 1) from (irb):327 lambda actual => nil => OK 0 for 1 Proc#call: (irb):342: warning: multiple values for a block parameter (0 for 1) from (irb):332 lambda actual => nil => OK 2 for 1 yield : (irb):342: warning: multiple values for a block parameter (2 for 1) from (irb):327 lambda actual => ["a", "b"] => OK 2 for 1 Proc#call: (irb):342: warning: multiple values for a block parameter (2 for 1) from (irb):332 lambda actual => ["a", "b"] => OK 3 for 1 yield : (irb):342: warning: multiple values for a block parameter (3 for 1) from (irb):327 lambda actual => ["a", "b", "c"] => OK 3 for 1 Proc#call: (irb):342: warning: multiple values for a block parameter (3 for 1) from (irb):332 lambda actual => ["a", "b", "c"] => OK 0 for 2 yield : lambda actual => nil, nil => OK 0 for 2 Proc#call: => NG ArgumentError wrong number of arguments (0 for 2) 1 for 2 yield : lambda actual => "a", nil => OK 1 for 2 Proc#call: => NG ArgumentError wrong number of arguments (1 for 2) 3 for 2 yield : lambda actual => "a", "b" => OK 3 for 2 Proc#call: => NG ArgumentError wrong number of arguments (3 for 2) 0 for 3 yield : lambda actual => nil, nil, nil => OK 0 for 3 Proc#call: => NG ArgumentError wrong number of arguments (0 for 3) 1 for 3 yield : lambda actual => "a", nil, nil => OK 1 for 3 Proc#call: => NG ArgumentError wrong number of arguments (1 for 3) 2 for 3 yield : lambda actual => "a", "b", nil => OK 2 for 3 Proc#call: => NG ArgumentError wrong number of arguments (2 for 3) 0 for -1 yield : lambda actual => [] => OK 0 for -1 Proc#call: lambda actual => [] => OK 1 for -1 yield : lambda actual => ["a"] => OK 1 for -1 Proc#call: lambda actual => ["a"] => OK 2 for -1 yield : lambda actual => ["a", "b"] => OK 2 for -1 Proc#call: lambda actual => ["a", "b"] => OK 3 for -1 yield : lambda actual => ["a", "b", "c"] => OK 3 for -1 Proc#call: lambda actual => ["a", "b", "c"] => OK 0 for -1 yield : Proc.new actual N/A => OK 0 for -1 Proc#call: Proc.new actual N/A => OK 1 for -1 yield : Proc.new actual N/A => OK 1 for -1 Proc#call: Proc.new actual N/A => OK 2 for -1 yield : Proc.new actual N/A => OK 2 for -1 Proc#call: Proc.new actual N/A => OK 3 for -1 yield : Proc.new actual N/A => OK 3 for -1 Proc#call: Proc.new actual N/A => OK 0 for 1 yield : (irb):347: warning: multiple values for a block parameter (0 for 1) from (irb):327 Proc.new actual => nil => OK 0 for 1 Proc#call: (irb):347: warning: multiple values for a block parameter (0 for 1) from (irb):332 Proc.new actual => nil => OK 2 for 1 yield : (irb):347: warning: multiple values for a block parameter (2 for 1) from (irb):327 Proc.new actual => ["a", "b"] => OK 2 for 1 Proc#call: (irb):347: warning: multiple values for a block parameter (2 for 1) from (irb):332 Proc.new actual => ["a", "b"] => OK 3 for 1 yield : (irb):347: warning: multiple values for a block parameter (3 for 1) from (irb):327 Proc.new actual => ["a", "b", "c"] => OK 3 for 1 Proc#call: (irb):347: warning: multiple values for a block parameter (3 for 1) from (irb):332 Proc.new actual => ["a", "b", "c"] => OK 0 for 2 yield : Proc.new actual => nil, nil => OK 0 for 2 Proc#call: Proc.new actual => nil, nil => OK 1 for 2 yield : Proc.new actual => "a", nil => OK 1 for 2 Proc#call: Proc.new actual => "a", nil => OK 3 for 2 yield : Proc.new actual => "a", "b" => OK 3 for 2 Proc#call: Proc.new actual => "a", "b" => OK 0 for 3 yield : Proc.new actual => nil, nil, nil => OK 0 for 3 Proc#call: Proc.new actual => nil, nil, nil => OK 1 for 3 yield : Proc.new actual => "a", nil, nil => OK 1 for 3 Proc#call: Proc.new actual => "a", nil, nil => OK 2 for 3 yield : Proc.new actual => "a", "b", nil => OK 2 for 3 Proc#call: Proc.new actual => "a", "b", nil => OK 0 for -1 yield : Proc.new actual => [] => OK 0 for -1 Proc#call: Proc.new actual => [] => OK 1 for -1 yield : Proc.new actual => ["a"] => OK 1 for -1 Proc#call: Proc.new actual => ["a"] => OK 2 for -1 yield : Proc.new actual => ["a", "b"] => OK 2 for -1 Proc#call: Proc.new actual => ["a", "b"] => OK 3 for -1 yield : Proc.new actual => ["a", "b", "c"] => OK 3 for -1 Proc#call: Proc.new actual => ["a", "b", "c"] => OK