Rubyのブロック構文の書き分け(do end,{})

do endと{}の違い

Rubyにおけるブロックはdo endと{}の二通りがあり,基本的にどちらを使っても同じようにブロックを記述することができるが,両者にはdo endより{}の方が結合が強いという違いがある.

# fの引数はa,b,ブロック
f a, b do end

# bの引数はブロック
# fの引数はaとbの戻り値
f a, b {}

検証コードは以下の通り.ただし,混乱を避けるため以下のようなdo endと{}で動作が変わるコードは書かないようにするべき

a = 1
b = 2

def f(*args)
  puts "f: args: #{args}, block: #{block_given?}"
  "f"
end

def b(*args)
  puts "b: args: #{args}, block: #{block_given?}"
  "b"
end

# fの引数はa,b,ブロック
f a, b do end
#=> f: args: [1, 2], block: true

# bの引数はブロック
# fの引数はaとbの戻り値
f a, b {}
#=> b: args: [], block: true
#=> f: args: [1, "b"], block: false

do endと{}の書き分け

以上のような結合の強さが理由でdo endと{}で動作が変わるコードは書くべきではない(上の例ではメソッドの引数リストを括弧でくくるべき).

このことを踏まえた上で,私なりのdo endと{}の書き分け方を以下に列挙してみる.初めてのRubyを参考にしているので,これと同じような書き分け方を実践している人は多いのではないかと思う.{}を使う時はコードを見やすくしたり何らかの意図を持たせる場合が多い.

  • {}
    • ブロック付きメソッドがインラインの場合
    • ブロック付きメソッドの戻り値を利用する場合
    • ブロック付きメソッドからさらにメソッドチェーンする場合
    • リソース管理のためにブロックを使う場合
  • do end
    • 上記以外の場合
{}:ブロック付きメソッドがインラインの場合
[1, 2, 3].each {|e| puts e}
{}:ブロック付きメソッドの戻り値を利用する場合
cubic = [1, 2, 3].map {|e| e ** 3}
{}:ブロック付きメソッドからさらにメソッドチェーンする場合
(1..10).select {|e| e.even?}.map {|e| e ** 3}
{}:リソース管理のためにブロックを使う場合

ブロックを抜け出す時にブロック引数のオブジェクトが自動的に解放される場合.

open("foo.txt") {|f|
  puts f.read
}
do end:上記以外の場合
[1, 2, 3].each do |e|
  puts e
end

以上

ブロック構文はよく使うので,こんな具合にdo endと{}の違いに意味を持たせてうまく使い分けるとソースが読みやすくなるかもね.