Rubyは柔軟性と表現力に富んだプログラミング言語であり、その強力な機能の一つに「クロージャ」があります。クロージャとは、その周囲の環境(スコープ)を「閉じ込める」ことができる関数のことです。Rubyにおけるクロージャには主に3つの形式があります:Block、Proc、およびLambda。これらは似ているようでいて、実は重要な違いがあります。この記事では、これらの違いを明確にし、それぞれの使用例を通じて理解を深めます。
Blockの基本
BlockはRubyで最もよく使われるクロージャの形式です。Blockは {}
または do...end
で定義され、メソッド呼び出しに際して、これを引数として渡すことができます。引数を渡すには、{}
またはdo
の後で引数リストを|
で囲みます。
例:
[1, 2, 3].each { |number| puts number }
この例では、each メソッドにBlockが渡され、配列の各要素が出力されます。
メソッドにブロックを渡す
ブロックはメソッドを呼び出す時のみ記述でき、メソッドの内部でyield
を使用することでブロックの内部で記述した処理が呼び出せます。yield
があるのにブロックが渡されない場合にはエラーが発生します。
def func(num)
num + yield
end
func(2) { 3 } # => 5
func(2) # => LocalJumpError: no block given (yield)
メソッドにブロックが渡されたかどうかの確認
メソッドにブロックが渡されたかどうかの確認は、block_given?
を使います。
def func
p block_given?
end
func { 'hoge' } # => true
func # => false
ブロックに引数を渡す
ブロックに引数を渡すこともできます。
def func(a, b)
a + yield(b, 1)
end
p func(1, 2) { |x, y| x + y } # => 4
ちょっと複雑ですが、func
に1
と2
を渡します。func
の中では、第1引数の値1とブロックの実行結果を合計します。yield
はブロック引数の2と1を合計して3を返します。従って実行結果は4になります。
Procの基本
Procはブロックをオブジェクト化したものです。ProcはProcクラスのコンストラクタにブロックを指定して生成します。そして実行するにはProcのインスタンスに対してcall
メソッドを呼びます。
pr = Proc.new { p 'hoge' }
pr.call # => 'hoge'
Proc.new
の代わりにproc
で定義することもできます。
pr = proc { p 'hoge' }
Procに引数を渡す
Procオプジェクトを作成する時に、仮引数を設定することができます。
pr = proc { |arg| p arg }
pr.call('hoge') # => 'hoge'
BlockとProcを相互に変換する方法
Blockへの変換
Procオブジェクトに&
を付けて、最後の引数に指定するとブロックに変換されます。
def func(x)
x + yield
end
pr = proc { 5 }
func(2, &pr) # => 7
Procへの変換
ブロックをProcオブジェクトとして受け取るには、最後の仮引数に&
を付けます。参考時には&
を付けないようにご注意ください。
def func(x, &pr)
x + pr.call
end
func(10) { 5 } # => 15
Lambdaの基本
lambdaメソッドはProcインスタンスを生成するが、Procと異なる動きがあります。
lmb = ->(x) { p x }
lmb.call('hoge') # => "hoge"
Procとlambdaの違い
Procとlambdaの違いは主に以下の2点です。lambdaによって作成されたものの方がメソッドに近い動きです。
相違点1:引数のチェック
Procの場合は、渡す引数が多いと先頭からの順番で取ります、渡す引数が少ないと足りない部分にnil
を割り当てます。
pr = proc { |a, b| p a, b }
pr.call # => [nil, nil]
pr.call(1) # => [1, nil]
pr.call(1, 2) # => [1, 2]
pr.call(1, 2, 3) # => [1, 2]
lambdaの場合は、メソッドみたいに、引数の数が違うと例外が発生します。
lmb = ->(a, b) { p a, b }
lmb.call(1) # => ArgumentError: wrong number of arguments (given 1, expected 2)
lmb.call(1, 2) # => [1, 2]
相違点2:リターンの挙動
Proc中のreturn
は生成元のスクープを脱出します。(メソッド自体を抜けます)
def func
pr = proc { return p 'hoge' }
pr.call
p 'fuga' # これは実行されない
end
func # => 'hoge'
lambda中のreturn
はそのブロック内でリターンすると呼出元に復帰します。(メソッドに戻り、メソッドの最後まで実行します)
def func
lmb = -> { return p 'hoge' }
lmb.call
p 'fuga' # ここは実行されます
end
func # => 'hoge'
# => 'fuga'
結論
特徴 | Block | Proc | Lambda |
---|---|---|---|
定義方法 |
{} または do...end
|
Proc.new または proc
|
lambda または ->
|
引数のチェック | チェックしない | チェックしない | チェックする |
リターンの挙動 | メソッドから抜け出す | メソッドから抜け出す | 呼び出し元に戻る |
呼び出し方法 | 自動で呼び出される |
call メソッドを使用する |
call メソッドを使用する |
主な使用例 | 簡単なイテレーションやコールバック | 複数回再利用可能なクロージャ | 明示的なクロージャと引数チェックが必要な場合 |
作成されたオブジェクト | 作成されない |
Proc クラスのインスタンス |
Proc クラスのインスタンス(サブタイプ) |
RubyのBlock、Proc、Lambdaはコードの柔軟性と抽象化を高める強力なツールです。それぞれの特性を理解し、適切な場面で使用することで、効率的かつ表現力豊かなコードを書くことができます。