使うだけでは忘れやすい、Rubyのブロックの仕組みについてまとめておく


Rubyのブロック記法は実業務上でもよく使いますが、ともすると「使うだけ」になってしまって、内部でどのようなことが起こっているのか?自分でブロックを渡せるメソッドを設計するにはどうするか?ということを忘れがちになります。

そこで、ブロックに関する仕組みについてまとめてみました。

日常的によく使うブロック

配列の処理などでEnumerable#each でブロック中に処理を書く記法は頻出ですね。

ary = %w(a b c)

ary.each do |elem|
    puts elem
end

# 1行で記述するなら、{}を使うほうが推奨される
ary.each {|elem| puts elem}

ブロックを受け取るメソッドを定義する

ブロックを取り扱うメソッドを定義したい場合は、ブロックを渡したい箇所にyieldを用いることで定義できます。


# ブロックを受け取る関数を定義
def awesome_method
   puts '----start----'
   yield
   puts '----end-----'
end

# 実際に呼び出す
awesome_method do
    puts 'Block method Called!'
end
# ----start----
# Block method Called!
# ----end-----

ブロックを渡されたかどうかを判定・分岐する

ただ単にyieldを呼び出すと、ブロックを渡されなかった時エラー(LocalJumpError)になります。

ブロックを渡されたかどうかを判定するには、block_given?メソッドを使います。

def awesome_method
   puts '----start----'
   if block_given?
     yield
   else
     puts 'no block given.'
   end
   puts '----end-----'
end

awesome_method # ブロックなしでメソッドを呼び出す

# ----start----
# no block given.
# ----end-----

yieldに引数を渡す

yieldには下記のようにして引数を渡すことが出来ます。


def awesome_method
   puts '----start----'
   if block_given?
     yield Time.now
   else
     puts 'no block given.'
   end
   puts '----end-----'
end

awesome_method do |time|
    puts "now is #{time.to_s}"
end

# ----start----
# now is 2017-10-08 08:36:53 +0000
# ----end-----

なお、ブロックは呼び出し時に渡された引数の数がブロックの仮引数と異なっていてもエラーになりません。

Procオブジェクトを使ったブロック実行

メソッドの呼び出しで引数の頭に&をつけると、その引数をブロックとして渡すことができます。

def awesome_method(&block)
   puts '----start----'
   block.call if block
   puts '----end-----'
end

bk = Proc.new do
    puts 'block given'
end

awesome_method &bk

Procオブジェクト以外を&で渡した場合は、引数のto_procメソッドを呼んだ結果が実行されます。


class Hoge
   
   def initialize(str='hoge')
       @str=str
   end
   
   def to_proc
       Proc.new { puts @str.upcase }
   end
end

def awesome_method(&block)
   puts '----start----'
   block.call if block
   puts '----end-----'
end

hoge = Hoge.new

awesome_method(&hoge)
# => 'HOGE'

よくある記法

下記のコードは、Enumerable#mapを用いて配列の中の要素をすべて大文字に変換しています。

ary = %w(a b c)

puts ary.map(&:upcase) #=> A B C

これは、内部的にはSymbol#to_procを呼び出した結果を実行しています。