これまでの章で、Rubyの強力な機能である「ブロック」を each や map などのメソッドと共に使ってきました。しかし、ブロックは常にメソッド呼び出しに付随する形でしか使えませんでした。
この章では、そのブロックを「オブジェクト」として扱い、変数に代入したり、メソッドの引数として自由に受け渡したりする方法を学びます。これにより、Rubyの表現力はさらに向上します。
ブロックは、それ自体ではオブジェクトではありません。しかし、Rubyにはブロックをオブジェクト化するための Proc クラスが用意されています。
Proc.new にブロックを渡すことで、Proc オブジェクトを作成できます。
作成した Proc オブジェクトは、call メソッドを使って実行できます。
proc という Proc.new のエイリアスメソッドもよく使われます。
Proc オブジェクトを作成するもう一つの方法として lambda があります。(-> というリテラル構文もよく使われます)
lambda で作成されたオブジェクトも Proc クラスのインスタンスですが、Proc.new (または proc) で作成されたものとは、主に以下の2点で挙動が異なります。
return は、Procが定義されたスコープ(通常はメソッド)からリターンします(ローカルリターン)。
return は、lambda ブロックの実行からリターンするだけです(Procからのリターン)。これは、メソッド内で Proc オブジェクトを定義して実行すると、その違いが明確になります。
Proc.new の例:
ruby proc_return_example.rbproc_return_test メソッド内の my_proc.call が実行された時点で、Proc内の return が呼ばれ、メソッド自体が終了していることがわかります。
lambda の例:
ruby lambda_return_example.rblambda の場合、my_lambda.call 内の return は lambda の実行を終了させ、その戻り値が result 変数に代入されます。メソッドの実行は継続します。
nil になり、余分な引数は無視されます。
ArgumentError が発生します。Proc.new の例:
lambda の例:
一般的に、lambda の方が通常のメソッド定義に近い(引数が厳密で、return がブロックから抜けるだけ)挙動をするため、使い分けが重要です。
& 演算子は、ブロックと Proc オブジェクトを相互に変換する役割を果たします。
メソッド定義の最後の引数に & をつけて引数名(慣習的に block)を指定すると、そのメソッド呼び出し時に渡されたブロックが Proc オブジェクトに変換され、その変数に束縛されます。
ruby block_receiver.rbこれにより、受け取ったブロック(Proc)を、メソッド内で好きなタイミングで実行したり、他のメソッドに渡したりすることが可能になります。
逆に、メソッドを呼び出す際に、Proc オブジェクトを & 付きで渡すと、その Proc オブジェクトがブロックとしてメソッドに渡されます。
Array#map メソッドは通常ブロックを受け取りますが、Proc オブジェクトを & を使って渡すことができます。
これは、以下のコードと等価です。
& は、Proc とブロック(メソッド呼び出しに付随するコード)の間の架け橋となる重要な演算子です。
Proc オブジェクト(lambda も含む)の非常に重要な特性として、クロージャ (Closure) があります。
クロージャとは、Proc オブジェクトが、それが定義された時点のスコープ(環境)を記憶し、後で実行される際にもそのスコープ内の変数(ローカル変数など)にアクセスできる仕組みです。
JavaScriptなど、他の言語でクロージャに触れたことがあるかもしれません。Rubyの Proc も同様の機能を提供します。
ruby closure_example.rbcounter_generator メソッドが終了した後でも、返された lambda オブジェクト(counter1 や counter2)は、それぞれが定義された時点の count 変数を保持し続け、call されるたびにそれを更新できます。これがクロージャの力です。
Proc.new や proc で作成できます。
lambda または -> で作成できる Proc オブジェクトの一種です。proc はローカルリターン、lambda はProcからのリターン。proc は寛容、lambda は厳密。Proc として受け取り、メソッド呼び出しで使うと Proc をブロックとして渡します。Proc や lambda は、定義された時点のスコープ(ローカル変数など)を記憶し、後からでもアクセスできます。引数を2つ取り、その和を返す lambda を作成し、adder という変数に代入してください。その後、adder.call(5, 7) を実行して 12 が返ってくることを確認してください。
ruby practice9_1.rb数値の配列 numbers と、Proc オブジェクト processor を引数として受け取る apply_proc_to_array メソッドを定義してください。メソッド内では、配列の各要素に対して processor を実行し、その結果を標準出力に出力するようにしてください。
(ヒント: メソッド呼び出し側では & を使って Proc をブロックとして渡すか、メソッド定義側で & を使ってブロックを受け取るか、両方の方法が考えられます。ここでは Proc オブジェクトをそのまま引数として受け取り、call で実行してみてください。)
ruby practice9_2.rb