Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
10年弱前投稿 修正あり

Rubyにはなぜ「ブロック」があるの?

Rubyの特徴的な仕組み「ブロック」がなぜ生まれたのか、その必要性からブロックをひもとく。ブロックが分かりにくいと思っていた人には、なるほどと思える分かりやすい解説。

原文
Why does Ruby have blocks? | Virtuous Code (English)
翻訳依頼者
D98ee74ffe0fafbdc83b23907dda3665
翻訳者
D98ee74ffe0fafbdc83b23907dda3665 doublemarket
原著者への翻訳報告
未報告


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)

このような一般的な場合だと、このコードはとても簡潔になります。しかしこれでもまだ異論があるのではないでしょうか。

  1. procあちこちに現れるようになってしまいます。少しうざったい。
  2. 振る舞いを引数として与えられるようになるのは、ほぼ全く新しい言語構造を作ってしまえることになります。常に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も気に入ってくれるはず。よろしく。

次の記事
bashで素晴らしく生産性を上げるための10のテクニック
前の記事
InnoDBの分離レベルによるMySQLのパフォーマンスへの影響

Feed small 記事フィード

新着記事Twitterアカウント