Cuộc sống Nhật Bản
Yuto Blog

RubyのBlock、Proc、Lambdaの違い、分かりやすく解説

Nội dung chính

Rubyは柔軟性と表現力に富んだプログラミング言語であり、その強力な機能の一つに「クロージャ」があります。クロージャとは、その周囲の環境(スコープ)を「閉じ込める」ことができる関数のことです。Rubyにおけるクロージャには主に3つの形式があります:BlockProc、およびLambda。これらは似ているようでいて、実は重要な違いがあります。この記事では、これらの違いを明確にし、それぞれの使用例を通じて理解を深めます。

Blockの基本

BlockはRubyで最もよく使われるクロージャの形式です。Blockは {} または do...end で定義され、メソッド呼び出しに際して、これを引数として渡すことができます。引数を渡すには、{}またはdoの後で引数リストを|で囲みます。

例:

Copy ruby
1
[1, 2, 3].each { |number| puts number }

この例では、each メソッドにBlockが渡され、配列の各要素が出力されます。

メソッドにブロックを渡す

ブロックはメソッドを呼び出す時のみ記述でき、メソッドの内部でyieldを使用することでブロックの内部で記述した処理が呼び出せます。yieldがあるのにブロックが渡されない場合にはエラーが発生します。

Copy ruby
1
2
3
4
5
6
def func(num)
  num + yield
end

func(2) { 3 } # => 5
func(2) # => LocalJumpError: no block given (yield)

メソッドにブロックが渡されたかどうかの確認

メソッドにブロックが渡されたかどうかの確認は、block_given?を使います。

Copy ruby
1
2
3
4
5
6
def func
  p block_given?
end

func { 'hoge' } # => true
func # => false

ブロックに引数を渡す

ブロックに引数を渡すこともできます。

Copy ruby
1
2
3
4
5
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メソッドを呼びます。

Copy ruby
1
2
pr = Proc.new { p 'hoge' }
pr.call # => 'hoge'

Proc.newの代わりにprocで定義することもできます。

Copy ruby
1
pr = proc { p 'hoge' }

Procに引数を渡す

Procオプジェクトを作成する時に、仮引数を設定することができます。

Copy ruby
1
2
pr = proc { |arg| p arg }
pr.call('hoge') # => 'hoge'

BlockとProcを相互に変換する方法

Blockへの変換

Procオブジェクトに&を付けて、最後の引数に指定するとブロックに変換されます。

Copy ruby
1
2
3
4
5
6
def func(x)
  x + yield
end

pr = proc { 5 }
func(2, &pr) # => 7

Procへの変換

ブロックをProcオブジェクトとして受け取るには、最後の仮引数に&を付けます。参考時には&を付けないようにご注意ください。

Copy ruby
1
2
3
4
5
def func(x, &pr)
  x + pr.call
end

func(10) { 5 } # => 15

Lambdaの基本

lambdaメソッドはProcインスタンスを生成するが、Procと異なる動きがあります。

Copy ruby
1
2
lmb = ->(x) { p x }
lmb.call('hoge') # => "hoge"

Procとlambdaの違い

Procとlambdaの違いは主に以下の2点です。lambdaによって作成されたものの方がメソッドに近い動きです。

相違点1:引数のチェック

Procの場合は、渡す引数が多いと先頭からの順番で取ります、渡す引数が少ないと足りない部分にnilを割り当てます。

Copy ruby
1
2
3
4
5
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の場合は、メソッドみたいに、引数の数が違うと例外が発生します。

Copy ruby
1
2
3
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は生成元のスクープを脱出します。(メソッド自体を抜けます)

Copy ruby
1
2
3
4
5
6
7
def func
  pr = proc { return p 'hoge' }
  pr.call
  p 'fuga' # これは実行されない
end

func # => 'hoge'

lambda中のreturnはそのブロック内でリターンすると呼出元に復帰します。(メソッドに戻り、メソッドの最後まで実行します)

Copy ruby
1
2
3
4
5
6
7
8
def func
  lmb = -> { return p 'hoge' }
  lmb.call
  p 'fuga' # ここは実行されます
end

func # => 'hoge'
     # => 'fuga'

結論

RubyのBlock、Proc、Lambdaはコードの柔軟性と抽象化を高める強力なツールです。それぞれの特性を理解し、適切な場面で使用することで、効率的かつ表現力豊かなコードを書くことができます。

Created at 2023-08-22
Nếu bài viết có ích thì các bạn hãy chia sẻ nhé
Rate this article: 4.9/5 (34 ratings)
You didn't rate yet
Le Minh Thien Toan

Tác giả:Yuto Yasunaga

Xin chào các bạn. Mình là kỹ sư IT đang làm việc ở Nhật Bản. Mình tạo blog này để chia sẻ về cuộc sống và những kinh nghiệm trong quá trình học tập và làm việc.
Hy vọng bài viết này sẽ có ích cho bạn.