ブロックのdo〜endは制御構造に、{}は式に

{ } と do end の違い - gan2 の Ruby 勉強日記

require 'benchmark'

puts Benchmark::CAPTION
puts Benchmark.measure {
  a = [1, 2, 3]
  a.replace [4, 5, 6]
}

このように { } を使うのは OK なのに


{ } を do end に変えると

require 'benchmark'

puts Benchmark::CAPTION
puts Benchmark.measure do
  a = [1, 2, 3]
  a.replace [4, 5, 6]
end

`measure': no block given (LocalJumpError)

ってエラーが出る。

そう、ふたつのブロック構文{}とdo〜endの優先順位の違い。
「puts Benchmark.measure do 〜 end」はKernel#putsにBenchmark.measureという引数とブロックを渡していると解釈される。だからBenchmark.measureにブロックがないぞーっと怒られる。

一般にdo〜endは制御構造…つまり、返り値は使わない副作用のみ利用する場合に使うとよい。Integer#timesやeachメソッドなんかがいい例。まあ俺ならブロックが1行ならば{}を使うけどそのへんは好みで…

3.times do |i|
  puts i
end
# >> 0
# >> 1
# >> 2

対してEnumerable#map等のように返り値を使うブロック付きメソッドは{}を使おう。返り値を使うということは、「式」として見ているので、{}の方が式らしく見える。Rubyは美観にこだわっている。

a = [1,2,3]
puts a.map {|x| x*3 }
# >> 3
# >> 6
# >> 9

こっちだと明らかに「a.map {|x| x*3}」がputsの引数になっているよね。さきほどのBenchmark.measureも同じこと。

ちなみに{}とdo〜endの問題が顕著になるのはRakefileだ。taskメソッドに:hogehogeという引数とブロックを渡すコードは

task :hogehoge do
  puts "hogehoge"
end

と書くけれど、

task :hogehoge {
  puts "hogehoge"
}

こっちはSyntaxErrorになる。

追記

スーパーPRE記法、使ってみます。素のw3mブラウジングしています。
Emacs Lisp書かなくては…