my.code();

my.code();

  • Python
    • 1.環境構築と基本思想
    • 2.基本構文とデータ型
    • 3.リスト、タプル、辞書、セット
    • 4.制御構文と関数
    • 5.モジュールとパッケージ
    • 6.オブジェクト指向プログラミング
    • 7.ファイルの入出力とコンテキストマネージャ
    • 8.例外処理
    • 9.ジェネレータとデコレータ
  • Ruby
    • 1.rubyの世界へようこそ
    • 2.基本構文とデータ型
    • 3.制御構造とメソッド定義
    • 4.すべてがオブジェクト
    • 5.コレクション (Array, Hash, Range)
    • 6.ブロックとイテレータ
    • 7.クラスとオブジェクト
    • 8.モジュールとMix-in
    • 9.Proc, Lambda, クロージャ
    • 10.標準ライブラリの活用
    • 11.テスト文化入門
    • 12.メタプログラミング入門
  • C++
    • 1.C++の世界へようこそ
    • 2.型システムとメモリ
    • 3.関数と参照
    • 4.ポインタと動的メモリ
    • 5.クラスの基礎
    • 6.クラスを使いこなす
    • 7.継承とポリモーフィズム
    • 8.テンプレート
    • 9.STL ①:コンテナ
    • 10.STL ②:アルゴリズムとラムダ式
    • 11.RAIIとスマートポインタ
    • 12.プロジェクトの分割とビルド

第9章: Proc, Lambda, クロージャ

これまでの章で、Rubyの強力な機能である「ブロック」を each や map などのメソッドと共に使ってきました。しかし、ブロックは常にメソッド呼び出しに付随する形でしか使えませんでした。

この章では、そのブロックを「オブジェクト」として扱い、変数に代入したり、メソッドの引数として自由に受け渡したりする方法を学びます。これにより、Rubyの表現力はさらに向上します。

ブロックをオブジェクトとして扱う: Proc クラス

ブロックは、それ自体ではオブジェクトではありません。しかし、Rubyにはブロックをオブジェクト化するための Proc クラスが用意されています。

Proc.new にブロックを渡すことで、Proc オブジェクトを作成できます。

作成した Proc オブジェクトは、call メソッドを使って実行できます。

proc という Proc.new のエイリアスメソッドもよく使われます。

Proc.new と lambda の違い

Proc オブジェクトを作成するもう一つの方法として lambda があります。(-> というリテラル構文もよく使われます)

lambda で作成されたオブジェクトも Proc クラスのインスタンスですが、Proc.new (または proc) で作成されたものとは、主に以下の2点で挙動が異なります。

1\. return の挙動

  • Proc.new (proc): return は、Procが定義されたスコープ(通常はメソッド)からリターンします(ローカルリターン)。
    • lambda: return は、lambda ブロックの実行からリターンするだけです(Procからのリターン)。

これは、メソッド内で Proc オブジェクトを定義して実行すると、その違いが明確になります。

Proc.new の例:

proc_return_example.rb
ruby proc_return_example.rb

proc_return_test メソッド内の my_proc.call が実行された時点で、Proc内の return が呼ばれ、メソッド自体が終了していることがわかります。

lambda の例:

lambda_return_example.rb
ruby lambda_return_example.rb

lambda の場合、my_lambda.call 内の return は lambda の実行を終了させ、その戻り値が result 変数に代入されます。メソッドの実行は継続します。

2\. 引数の厳密さ

  • Proc.new (proc): 引数の数に寛容です。足りない引数は nil になり、余分な引数は無視されます。
    • lambda: 引数の数を厳密にチェックします。過不足があると ArgumentError が発生します。

Proc.new の例:

lambda の例:

一般的に、lambda の方が通常のメソッド定義に近い(引数が厳密で、return がブロックから抜けるだけ)挙動をするため、使い分けが重要です。

& 演算子の役割

& 演算子は、ブロックと Proc オブジェクトを相互に変換する役割を果たします。

1\. ブロックを Proc として受け取る

メソッド定義の最後の引数に & をつけて引数名(慣習的に block)を指定すると、そのメソッド呼び出し時に渡されたブロックが Proc オブジェクトに変換され、その変数に束縛されます。

block_receiver.rb
ruby block_receiver.rb

これにより、受け取ったブロック(Proc)を、メソッド内で好きなタイミングで実行したり、他のメソッドに渡したりすることが可能になります。

2\. Proc をブロックとして渡す

逆に、メソッドを呼び出す際に、Proc オブジェクトを & 付きで渡すと、その Proc オブジェクトがブロックとしてメソッドに渡されます。

Array#map メソッドは通常ブロックを受け取りますが、Proc オブジェクトを & を使って渡すことができます。

これは、以下のコードと等価です。

& は、Proc とブロック(メソッド呼び出しに付随するコード)の間の架け橋となる重要な演算子です。

クロージャ(スコープ)の概念

Proc オブジェクト(lambda も含む)の非常に重要な特性として、クロージャ (Closure) があります。

クロージャとは、Proc オブジェクトが、それが定義された時点のスコープ(環境)を記憶し、後で実行される際にもそのスコープ内の変数(ローカル変数など)にアクセスできる仕組みです。

JavaScriptなど、他の言語でクロージャに触れたことがあるかもしれません。Rubyの Proc も同様の機能を提供します。

closure_example.rb
ruby closure_example.rb

counter_generator メソッドが終了した後でも、返された lambda オブジェクト(counter1 や counter2)は、それぞれが定義された時点の count 変数を保持し続け、call されるたびにそれを更新できます。これがクロージャの力です。

☕ この章のまとめ

  • Proc: ブロックをオブジェクト化したもので、Proc.new や proc で作成できます。
    • Lambda: lambda または -> で作成できる Proc オブジェクトの一種です。
    • Proc と Lambda の違い:
      • return: proc はローカルリターン、lambda はProcからのリターン。
      • 引数: proc は寛容、lambda は厳密。
    • & 演算子: メソッド定義で使うとブロックを Proc として受け取り、メソッド呼び出しで使うと Proc をブロックとして渡します。
    • クロージャ: Proc や lambda は、定義された時点のスコープ(ローカル変数など)を記憶し、後からでもアクセスできます。

練習問題1: Lambda の作成

引数を2つ取り、その和を返す lambda を作成し、adder という変数に代入してください。その後、adder.call(5, 7) を実行して 12 が返ってくることを確認してください。

practice9_1.rb
ruby practice9_1.rb

練習問題2: & を使ったメソッド

数値の配列 numbers と、Proc オブジェクト processor を引数として受け取る apply_proc_to_array メソッドを定義してください。メソッド内では、配列の各要素に対して processor を実行し、その結果を標準出力に出力するようにしてください。 (ヒント: メソッド呼び出し側では & を使って Proc をブロックとして渡すか、メソッド定義側で & を使ってブロックを受け取るか、両方の方法が考えられます。ここでは Proc オブジェクトをそのまま引数として受け取り、call で実行してみてください。)

practice9_2.rb
ruby practice9_2.rb