ブロック付きメソッドを定義する
前のページでは each
や times
といった標準のイテレータにブロックを渡して来ましたが、次はブロックを引数に取るメソッドを独自に定義してみましょう。
方法は簡単で、メソッド定義時に yield
というキーワードを含めるだけです。yieldの箇所で渡したブロックが実行され、さらにyieldの使用回数に制限はありません。
非情に単純です。
ちなみに例の最後で示したように、ブロックが渡されない場合はyieldの部分でno block given (yield) (LocalJumpError)
が発生します。これを避けるために Kernel.#block_given?
を使い、ブロックが渡されたときはyieldを実行するが、ブロックが渡されないときは別の動作をする(例えば任意の例外を投げる)ようなメソッドを定義できます。
さらに進み、each
などでお馴染みのブロック引数がある場合を見てみます。
「通常の引数」と「ブロック引数」が共存する場合は次のように書きます。
ブロックをオブジェクトとして扱う
さてここまで、ブロックは「引数なのにメソッド定義の引数リストには現れない」「yieldという特別なキーワードで実行される」など、ある意味「特別扱い」をされてきました。
次に「ブロックをオブジェクトとして扱うことができれば、より柔軟なコードが書けるのではないか」と考えを進めてみます。たとえば渡されたブロックをその場でyield実行する以外に、別のメソッドに渡して処理をたらい回しにしたり、などという使い方が想定できます。
実際、それは可能です。先に上げた iter
をもとに例を示します。
iterの定義時に&b
という「&
」付きの特殊な引数を書いておきます。bの素性を調べてみると、Procというクラスのインスタンスであることがわかります。これが、ブロックを表現するオブジェクトの正体です。
ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。
Procインスタンスに対しProc#call
メソッドを呼び出すことでコードを実行させます。
また、Procインスタンスはブロック付きメソッドの引数で実体化させる以外に、他のクラスと同じくProc.new
するか、Proc.new
と同じ意味のKernel.#proc
を使うことでも作成できます。
次のページではProc.new
と同じくProcインスタンスを生成する「lambda
」を紹介し、Procとの差異について解説します。