ベルリンのITスタートアップで働くジャバ・ザ・ハットリの日記

日本→シンガポール→ベルリンへと流れ着いたソフトウェアエンジニアのブログ

RubyのblockやProcを分かったつもりになっていて見事にハマった

反省した。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

つかれた。。。

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com