yieldとProc#callの違い

これらは似てるけど、引数の渡し方に違いがあるんですね。

まとめると、渡される引数の数と期待する引数の数が違うときは以下のように動きます。

  • 期待している引数が0個あるいは可変長のときは、yieldもProc#callも同じ動作
  • 期待している引数が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