ブロックのはなし

RubyのProcやlambdaについての記事は検索するとよく出てくるが、ブロックはなぜかいまいちスポットライトが当たらない。あってもProcの説明のオマケみたいな感じが関の山。
この記事ではそんな不遇なブロックについて焦点を当ててみたい。

ブロックとは何か

まずはここの認識を合わせておこう。Rubyの構文において、メソッド呼び出しの後ろにくっつくことがあるdo〜end、もしくは{〜}の間にあるコードの塊をブロックと呼ぶ。
すべてがオブジェクトであるRubyでも、言語の文法を構成する要素はオブジェクトではない。例えばifとか、代入式の=や、変数などはオブジェクトではない。当たり前といえば当たり前なのだが、同様に、文法を構成する要素であるブロックはオブジェクトではない。
Procオブジェクトと一緒に「似たようなもの」と説明されると地味に理解しづらいのだが、ProcはRubyの文法とは何の関係も無いひとつのクラスであり、そのインスタンスはひとつのオブジェクトである。Rubyの文法の要素でありオブジェクトではないブロックとは、そういう意味では根本的に異なる。

ブロックの例

さて、いまさらだがブロックの例を挙げておこう。

[1, 2, 3].each do |v|
  print v
end #=> 123

この、doからendまでのコード、「print v」てやつがブロックとなる。Array#eachは極めてメジャーなメソッドなのでこのコードの結果が予測できない人はまずいないだろう。

実装的な

Ruby処理系の実装としては、ブロックはブロックとして特殊な扱いをしている場合もあるし、メソッドを呼ぶ際に内部でProcオブジェクトを作ってしまう場合もある。Rubyインタプリタ内部でそれをオブジェクトとして扱うかどうかはあくまでも処理系実装の話であり、Rubyという言語においての話とは別と考えるべきだ。

yieldとblock_given?

ブロックを受け取ったメソッドはそのブロックをyieldで呼び出すことができる。ブロックを受け取ったかどうかはblock_given?メソッドで判定できる。

def hoge(fuga)
  if block_given?
    yield fuga
  else
    puts "ねーよ"
  end
end

hoge(5) do |v|
  puts v
end #=> 5

hoge(5) #=> ねーよ

しかし、このhogeメソッド内部において、ブロックはプログラマの目に見える形では出現しない。ブロックを受け取る明示的な記述も無い。このあたりがブロックに対する理解を阻む一因になっているのではないかと考える。

ブロックの扱い

Rubyのメソッドの解説などで「このメソッドはブロックを受け取る」といった表現をたびたび見かけるが、実際のところ、Rubyのメソッドは「ブロックを受け取らない」ということがありえない。これは例えば以下のコードを実行してもエラーにならないことから見て取れる。

def hoge
  "A"
end

foo = hoge do
  print 1
end

p foo #=> "A"

Rubyのメソッドはすべて、無条件に、ブロックを受け取ることができる。ただ、それを使うかどうかはメソッドによる。というわけだ。
ブロックを使わないメソッドはブロックを渡した場合にエラーにできるとよいのだが、Rubyという言語は徹底的に動的なので、yieldやblock_given?をメソッド内で呼ぶかどうかを静的に判断する手段が無い。なので、呼び出し側にブロックが記述されていればとりあえずメソッドに渡すが、それを使うかどうかはわからないし、実行した結果として使わなかったとしてもそれをもってエラーにすることはできない。
逆に、ブロックを受け取っていないのにyieldすると例外が発生する。ブロックを必要としないメソッドにブロックを渡した場合は無視すればいいが、ブロックを必要とするメソッドにブロックを渡さない場合は動作できない。よく考えるまでもない。
ちなみに、ユーザ定義のRubyで書かれたメソッドだけじゃなく、Rubyの組み込みクラスの組み込みメソッドでもこのブロックを無視する動作は同様である。

bar = "5".to_i do
  print 1
end

p bar #=> 5

Procとブロック

ブロックはブロックとして扱う限りProcオブジェクトを作らないので軽い、という話をどこかで昔見たことがあるような気がする。昔の話なので今はどうかわからないし、そもそも昔はどうだったかも知らないのだが。
ともあれ、ブロックはRuby処理系によっては内部でProcとして作ってしまうぐらい、その概念はProcオブジェクトに近い。Rubyにもブロック→Procオブジェクト変換の機能はあって、仮引数に&をつけて指定したり、Proc.newしたり、procメソッドを呼んだりすることで、ブロックをProcに変換して取得することができる。このへんの話はこちらの記事を見ると順を追って説明されているので興味がある人がどうぞ。
っていうか、通常Proc.newにブロックを渡してProcオブジェクトを作るんだから、ブロックがProcオブジェクトに変換できるのは当たり前すぎる話である。
ところで、逆に実引数に&をつけることでProcをブロックに変換することもできる。

def hoge
  yield
end

foo = Proc.new{p "A"}
hoge(&foo) #=> "A"

Procとブロックは相互変換可能な、かなり近いものであることがわかる。

おしまい

ブロックというのはRubyの文法要素のひとつであり、メソッドにコードを渡すことが簡単にできるようにと作られた記述方法である。JavaScriptみたいにいちいちfunctionとreturnを書くとかありえないが、RubyでもProc.new{〜}とかcallするのは面倒なのである。どんだけめんどくさがりなんだ?でもそのおかげで我々は高階関数をそれと気づかないうちに使うことができてしまう。
Ruby処理系内部の実装はさておき、Rubyという言語から見てみると、ブロックはそれをオブジェクトとしては見えないように隠しつつ、何かしら謎な方法でメソッドに渡して実行することができる。それが望ましい仕様かというとわからない、っていうかちょっと怪しい雰囲気はあるが、初心者のうちは「Procは難しいけどブロックとyieldならなんとか」って言う段階もあるわけで、悪いことばかりでもない。
ま、Procとブロックの用途が地味にかぶっていたりしても、どちらかだけでよい、ということはありえず、明確に美しく使い分けができるわけでもなく、歴史的事情を含みつつ、泥臭さも残りつつ。Rubyはそういうところがいいんじゃないのかな。などと思ったりするのだな。ああ、これではただのRubyオタクか。まあいいか。