Rubyのブロックは、この言語を使い始めたほとんどの人にとって、最初に超えなければならないハードルでしょう。他の言語を何年も使ってきた人にさえ、ブロックというコンセプトは最初は理解しにくいことが多いようです。
ブロックを完全に理解するのが難しいのは、その一般的な説明の仕方に問題があるのではないかと私は思います。本来Parleyへの投稿として始まったこの話に関して、この記事では私のブロックについての考えを、他の言語で既に使っているであろうパターンに対する実用的なショートカットとして説明したいと思います。
他の多くの言語と同じくRubyでは、受け渡しができ実行可能なコードの小さな塊を作ることができます。これを実現するにはたくさんの方法がありますが、この記事の目的に合わせて、proc
しかないものと考えましょう。
code = proc do |name|
puts "hello, #{name}"
end
code.call("Avdi")
# >> hello, Avdi
proc
は、データではなく振る舞いをメソッドに渡すのに便利であるという点で重宝します。
分かりやすい例を挙げると、ファイルをオープンする時、ファイルが開かれている間に実行したいコードのproc
を渡すことがあるでしょう。そうすることで、proc
が終了するとファイルは自動的に閉じられます。
code = proc do
# ...
end
open("myfile", "w", :while_open_do => code)
ここでメソッドに渡したproc
の形をした振る舞いは、ほとんどの場合1回しか実行しないコードでしょう。それをわざわざローカル変数に割り当てる理由はありません。そのため通常は、以下のように済ませてしまいます。
open("myfile", "w", :while_open_do => proc do
# ...
end)
JavaScriptをある程度知っているなら、JSのありふれた使い方である、メソッドの引数に匿名関数を渡すのと比べてみましょう。
$( "#target" ).click(function() {
alert( “Handler for .click() called.” );
});
データ引数だけでなく振る舞いの引数を取るこのようなメソッドを使っていると、9割の場合、メソッドは1つの振る舞いの引数しか取らないことに気づくでしょう。外部から定義してやる必要のあるひとつの振る舞いしか持たない単一責任の小さなメソッドに、何かがあるはずです。
それではここでこんなルールを考えてみましょう。あるメソッドが何らかの振る舞いを引数に取る時には、常に最後の引数に「コードのブロック」あるいは単に「ブロック」を取るようにする、というものです。
def open(filename, mode, block)
# ...
end
open("myfile", "w", proc do
# ...
end)
このような一般的な場合だと、このコードはとても簡潔になります。しかしこれでもまだ異論があるのではないでしょうか。
proc
があちこちに現れるようになってしまいます。少しうざったい。- 振る舞いを引数として与えられるようになるのは、ほぼ全く新しい言語構造を作ってしまえることになります。常に
proc
があるし、end
の後に必ず閉じ括弧が来るのでそうではないとは分かりますが。
実際のところ、閉じ括弧を必ずしも使わなくていい言語を普段使っていると、閉じ括弧にかなりイライラさせられることになるでしょう。
「引数のうちのひとつはコードのブロック」というルールをサポートするように、言語を少し変えてみたらどうでしょうか?どれがブロックなのかを表す特別なシンボルを付けてみましょう。そして、この言語では引数のリストのすぐ後にコードのブロックが来るようになっていれば、閉じ括弧のことも考えなくてよくなります。さらに、このルールを言語の文法にしてしまうなら、proc
キーワードも必要なくなります。
def open(filename, mode, &block)
# ...
end
open "myfile", "w" do
# ...
end
さて、ここまで来たら引数に明示的に指定されていなくてもコードのブロックを受け取れるようにしてしまえばいいのです。「ここで渡されるはずのコードのブロックの制御を受け取る」という意味でyield
を使いましょう。
def open(filename, mode)
# ...
yield
# ...
end
これでRubyのブロックの出来上がりです。覚えておかなければならない重要な点が、基本的には、振る舞いを渡すためのコードのブロックが引数のひとつであるメソッド、というよくあるイディオムに対するシンタックスシュガーであるということです。
(あなたが経験豊かなRubyプログラマなら、制御フローに関してもブロックの特異性があることを知っているかもしれませんが、ブロックは本質的にはショートカットであるというこの話の前提は変わらないでしょう。)
最後にこれは宣伝ですが、この記事を見つけてくれた人は、週にコーヒー1杯の値段でRubyの知識をレベルアップさせられるサイトRubyTapasも気に入ってくれるはず。よろしく。