Ruby

【Ruby】ブロック・Proc・lambda を理解する


概要

Rubyのブロック・Proc・Lambdaを理解する上で必要な基礎知識をまとめました。


ブロックとは?

ブロックとはdo~endもしくは{}で囲まれた処理のカタマリの事を指します。

以下のコードのdo |a| p a end, { |a| p a }がブロックと呼ばれる部分です。

3.times do |a|

p a
end
#=> 0 1 2

3.times { |a| p a }
#=> 0 1 2


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

メソッドの中でyieldを明示すると受け取ったブロックを実行します。

yieldがあるにも関わらずブロックが渡されない場合にはエラーが発生します。

def method1

yield
end

method1 { p 'block' } #=> "block"
method1 #=> `method1': no block given (yield) (LocalJumpError)

指定したメソッドにブロックが渡されたかどうかはblock_given?を利用する事で確かめる事ができます。

def method1

p block_given?
end

method1 { p 'block' } #=> true
method1 #=> false

ここまでの知識を応用して、ブロックが渡された時には実行し、それ以外の場合でエラーの発生をさせたくない場合は以下の様に書けます。

def method1

yield if block_given?
end

method1 { p 'block' } #=> "block"
method1 ##エラーが発生しない

また、ブロックに引数を渡す事もできます。

以下のコードのように、ブロック内で設定した仮引数(parameter)に対してyieldから実引数('arg')を渡す事が可能です。

def method2

yield('arg')
end

method2 { |parameter| p parameter } #=> "arg"


Procとは?


  • Procとはブロックを持ち運び便利なオブジェクトにしたものです。

  • ProcはクラスなのでProc.newでオブジェクトを作る事が出来ます。


  • Proc.newによって作成されたProcオブジェクトはcallで呼び出すことが出来ます。

proc1 = Proc.new { p "proc" } 

##Proc.newによってブロック({ p "proc" })をオブジェクト化したproc1を作成。

proc1.call #=> "proc"
##Proc.newによって作成されたproc1を.callで呼び出す。

Procオブジェクト作成の際に以下のように仮引数を設定する事が可能です。

proc1 = Proc.new { |par1| p par1 }

proc1.call("arg") #=> "arg"


ブロックを引数で明示的に受け取る方法

メソッドにブロックを渡す方法としてyieldを紹介ましたが、下記コードのように&から始まるブロック引数を使用する事でブロックを引数で明示的に受け取ることも可能です。

下記コードでは以下のような工程を経ています。


  • &が付いた引数(ブロック引数)に渡されたブロックをProcオブジェクトに変換する。(ブロックのオブジェクト化)

  • procオブジェクト.callによって渡されたオブジェクトを呼び出す。

def method2(&proc) ##ブロックのオブジェクト化

proc.call ##Procオブジェクトの呼び出し
end

method2{ p 'block' } #=> "block"


lambdaとは?

lambdaとはProcオブジェクトを作る方法の一つで、lambdaメソッドによって作成されたProcオブジェクトはProc.newで作成されたオブジェクトといくつか異なる点を持ちます。

Procの説明部分でProcオブジェクトを作成する方法の1つであるProc.newを紹介しましたが、実際には以下の様な複数の方法が存在します。lambdaもその内の1つです。



  • Proc.new{ } による方法


  • proc{ } による方法


  • lambda{ } による方法


  • ->{ }による方法(lambdaによって作成されたProcオブジェクトと同じ性質をもつオブジェクトを作成する。)

proc1 = Proc.new { |arg| puts arg } ## Proc.new{ } による方法

proc2 = proc { |arg| puts arg } ##proc{ } による方法
proc3 = lambda { |arg| puts arg } ##lambda{ } による方法
proc4 = ->(word) { puts word } ##->{ } による方法
proc5 = -> { puts "proc" } ##->{ } による方法


Proc.newとlambdaの相違点

lambdaによって作成されたProcオブジェクトとProc.newによって作成されたProcオブジェクトの相違点は主に以下の2点です。lambdaによって作成された物の方がよりメソッドに近い働きをします。


  • 引数チェック

  • returnの挙動


相違点①(引数チェック)

Proc.newの場合

Proc.newの場合は渡す引数が多いと、先頭から必要な数だけ取って後は無視し、少ないと足りない部分に nil を割り当てるため多重代入に近い扱い方をします。

proc1 = proc { |arg| p arg }

proc1.call( 'proc', 'lambda') #=> "proc"

lambdaの場合

lambda の場合は引数の数が違うとArgumentErrorになります。

lamd = lambda { |arg| p arg }

lamd.call('lambda') #=> "lambda"
lamd.call('lambda', 'proc') #=> wrong number of arguments (given 2, expected 1) (ArgumentError)


相違点②(returnの挙動)

Proc.newの場合

Proc.newの場合はreturn後にメソッド自体を抜けてしまいます。

def proc_method

proc = Proc.new { return p "proc"}
proc.call
p "proc method"
end

proc_method #=> "proc"

lambdaの場合

lambdaの場合はreturnした後にメソッドに戻り、メソッドを最後まで実行します。

def lambda_method

lambda1 = lambda{ return p "lambda"}
lambda1.call
p "lambda method"
end

lambda_method #=> "lambda"
#=> "lambda method"


参考

Ruby block/proc/lambdaの使いどころ

【Ruby】ブロックとProcとlambdaについて

Rubyの ブロック、Proc.new、lambdaの違い