Rubyブロック・メソッド呼び出し

  • b2args(1,&pr)というように、実引数のProcオブジェクトに&を付けてb2argsに渡している理由は、b2argsの仮引数に&をつけているため。Procオブジェクトをブロックに変換したものが、b2argsに渡した時点で再度Procオブジェクトに変換される。
  • yieldは、仮引数の最後に明示的に渡されたブロックか、あるいは仮引数になく暗黙的に渡されたブロックを受け取って実行することができる。
  • &で受けるProcオブジェクトや、lambdaオブジェクト、あるいは**で受けるキーワード引数は、仮引数の最後にしか書けない。
  • b2argslmにはlambdaオブジェクトをそのまま渡していて、lambdaオブジェクトのclassがProcであることが分かる。
  • Proc.newで作るProcオブジェクトとlambdaオブジェクトの違いは、ブロックに対して呼び出し時の引数の数が少ない場合に、Procオブジェクトではnilが入れられて実行が継続されるが、lambdaオブジェクトではArgumentErrorとなる。*1
  • block_given?を使えば、ブロックが渡された場合にTrueを返すので、ブロックが渡されたか渡されていないかで分岐した処理が書ける。

プログラム

$ ruby --version
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
$ cat proc.rb 
def b2args(i,&b)
    puts i.class
    puts b
    puts b.class

    puts "-- block.call --"
    b.call
    puts "-- block.call(i) --"
    b.call(i)
    puts "-- yield --"
    yield
    puts "-- yield(i) --"
    yield(i)

    puts "// the end of method"
end

def b2argslm(i,l)
    puts i.class
    puts l
    puts l.class

    puts "-- lambda.call(i) --"
    l.call(i)

    puts "// the end of method"
end

def b1arg(a)
    if block_given?
        puts a.class
 
        puts "-- yield --"
        yield
        puts "-- yield(a) --"
        yield(a)
    else
        puts "No block given"
    end
end


def b3argsany(a,*b,**c)
    puts a.class
    puts b.class
    puts c.class

    puts "-- yield(a,b,c) --"
    yield(a,b,c)
end

pr=Proc.new{|i|puts "pr called with #{i}"}
lm=lambda{|i|puts "lm called with #{i}"}

puts "== pr.call =="
pr.call
puts "== pr.call(1) =="
pr.call(1) 
puts ""

#puts "== lm.call =="
#lm.call # => ArgumentError
puts "== lm.call(1) =="
lm.call(1)
puts ""

puts "== b2args(1,&pr) =="
b2args(1,&pr)
puts ""

puts "== b2argslm(1,lm) =="
b2argslm(1,lm)
puts ""

puts "== b1arg(2){|a|...} =="
b1arg(2){|a|puts "b1arg called with #{a}"}
puts ""

puts "== b1arg(0) =="
b1arg(0)
puts ""

puts "== b3argsany([1,2,3]){|a,b,c|...} =="
b3argsany([1,2,3]){|a,b,c|puts "b3argsany called with #{a}, #{b} and #{c}"}
puts ""

puts "== b3argsany(*[1,2,3],{x:10}){|a,b,c|...} with multi-assignment =="
b3argsany(*[1,2,3],{x:10}){|a,b,c|puts "b3argsany called with #{a}, #{b} and #{c}"}
puts ""

puts "== b3argsany(0,{x:10},*[1,2,3]){|a,b,c|...} with multi-assignment =="
b3argsany(0,{x:10},[1,2,3]){|a,b,c|puts "b3argsany called with #{a}, #{b} and #{c}"}
puts ""

puts "== b3argsany({x:10},{y:20},{z:30},{p:11},{q:22},{r:33}){|a,b,c|...} with multi-assignment =="
b3argsany({x:10},{y:20},{z:30},{p:11},{q:22},{r:33}){|a,b,c|puts "b3argsany called with #{a}, #{b} and #{c}"}
puts ""

実行結果

$ ruby proc.rb 
== pr.call ==
pr called with 
== pr.call(1) ==
pr called with 1

== lm.call(1) ==
lm called with 1

== b2args(1,&pr) ==
Fixnum
#<Proc:0x007fb34511df78@proc.rb:47>
Proc
-- block.call --
pr called with 
-- block.call(i) --
pr called with 1
-- yield --
pr called with 
-- yield(i) --
pr called with 1
// the end of method

== b2argslm(1,lm) ==
Fixnum
#<Proc:0x007fb34511df50@proc.rb:48 (lambda)>
Proc
-- lambda.call(i) --
lm called with 1
// the end of method

== b1arg(2){|a|...} ==
Fixnum
-- yield --
b1arg called with 
-- yield(a) --
b1arg called with 2

== b1arg(0) ==
No block given

== b3argsany([1,2,3]){|a,b,c|...} ==
Array
Array
Hash
-- yield(a,b,c) --
b3argsany called with [1, 2, 3], [] and {}

== b3argsany(*[1,2,3],{x:10}){|a,b,c|...} with multi-assignment ==
Fixnum
Array
Hash
-- yield(a,b,c) --
b3argsany called with 1, [2, 3] and {:x=>10}

== b3argsany(0,{x:10},*[1,2,3]){|a,b,c|...} with multi-assignment ==
Fixnum
Array
Hash
-- yield(a,b,c) --
b3argsany called with 0, [{:x=>10}, [1, 2, 3]] and {}

== b3argsany({x:10},{y:20},{z:30},{p:11},{q:22},{r:33}){|a,b,c|...} with multi-assignment ==
Hash
Array
Hash
-- yield(a,b,c) --
b3argsany called with {:x=>10}, [{:y=>20}, {:z=>30}, {:p=>11}, {:q=>22}] and {:r=>33}

補記:Pythonのyield

  • Pythonのyieldは関数をiteratorにするジェネレータである

プログラムと実行結果

$ cat yield.py 
def func(n):
    i=1
    while i<=n:
        yield i
        i+=1
    #raise StopIteration #=> This is not needed.

f=func(5)
for i in f:
    print i

g=func(5)
print g.next()
print g.next()
print g.next()
print g.next()
print g.next()
try:
    print g.next()
except StopIteration:
    print "Exception: StopIteration"

l=list(func(5))
print l

$ python yield.py 
1
2
3
4
5
1
2
3
4
5
Exception: StopIteration
[1, 2, 3, 4, 5]

補記:JRuby

  • JRuby 1.7.21では、キーワード引数に対応していないため、下記のように実行エラーとなった。
$ ~/Downloads/jruby-1.7.21/bin/jruby proc.rb 
SyntaxError: proc.rb:38: syntax error, unexpected tPOW
def b3argsany(a,*b,**c)
                     ^
  • JRuby 9.0.0.0では実行エラーは起こらず、実行結果はRuby-2.2.2と同じになった。

*1:lambdaオブジェクトはブロック内のreturnが呼び出し元のコンテキストでは作用しない、という違いもある→Rubyの ブロック、Proc.new、lambdaの違い - Qiita