1
0

Rubyは柔軟性と表現力に富んだプログラミング言語であり、その強力な機能の一つに「クロージャ」があります。クロージャとは、その周囲の環境(スコープ)を「閉じ込める」ことができる関数のことです。Rubyにおけるクロージャには主に3つの形式があります:BlockProc、および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

ちょっと複雑ですが、func12を渡します。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はコードの柔軟性と抽象化を高める強力なツールです。それぞれの特性を理解し、適切な場面で使用することで、効率的かつ表現力豊かなコードを書くことができます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0