Help us understand the problem. What is going on with this article?

RubyのblockやProcをメソッドに渡す際にハマった

More than 3 years have passed since last update.

反省した。RubyのblockやProcを分かったつもりになっていて、しょうもないところでハマった。自戒を込めてブログに残しておくことにした。

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]

例1

def method_1
  if block_given?
    puts 'Yes'
    yield
  else
    puts 'No'
  end
end

method_1 { puts 'I am a block' }

つまりmethod_1に I am a block の出力というブロックを渡して、ブロックが有ればYesと共にそれを出せと。
method_1にはブロック引数が無いが、この例のようにそれが問題になることもなく、yieldすればちゃんと実行される。

実行結果

$ ruby block_sample_1.rb
Yes!
I am a block

例2

def method_1
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2
end

def method_2
  if block_given?
    puts 'Yes :method_2'
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

blockが渡されたmethod_1からmethod_2を呼び出す例。

実行結果

$ ruby block_sample_2.rb
Yes :method_1
No  :method_2

渡されたblockはmethod_1まで。method_2には到達していない。

例3

つまり&引数名にしてブロックをProcオブジェクト化して渡す必要がある。その対策後のコード例がこれ。

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 &block
end

def method_2
  if block_given?
    puts 'Yes :method_2'
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果
これでしっかりblockがProc化されてmethod_2にまで到達していることが分かる。

$ ruby block_sample_3.rb
Yes :method_1
Yes :method_2
I am a block

例4

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 plus_one 1, &block
end

def plus_one number
  number + 1
end

def method_2 number, &block
  if block_given?
    puts 'Yes :method_2'
    puts number
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果

$ ruby block_sample_4.rb
Yes :method_1
No  :method_2

method_2にまでブロックが到達していない。これでハマった。とくに例3と変わったことをしているようにも思えない。ただdef plus_one numberを加えただけで、そこにblockはまったく関係無さそう。なぜmethod_2にまでブロックが到達しないのか、しばらく分からなかった。

見つけた答えがこれ。

例5

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 plus_one(1), &block
end

def plus_one number
  number + 1
end

def method_2 number, &block
  if block_given?
    puts 'Yes :method_2'
    puts number
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果

$ ruby block_sample_5.rb
Yes :method_1
Yes :method_2
2
I am a block

例4との違いはここの()だけ。

  method_2 plus_one(1), &block

つかれた。。。

エンジニアの皆様へ

「ほとんどのエンジニアには解けるが、下位10%のダメなエンジニアにだけ解けないパズル?」なるものをシリーズ化してパズル1から8まで作成した。もしご興味あれば解いてみてください。
http://tango-ruby.hatenablog.com/entry/2015/11/30/122814

jabba
ベルリンのスタートアップで働くソフトウェアエンジニア(イボ痔持ち)
https://www.jabba.cloud/
admin-guild
「Webサービスの運営に必要なあらゆる知見」を共有できる場として作られた、運営者のためのコミュニティです。
https://admin-guild.slack.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away