TECH::EXPERT【Ruby】Procクラス【67日目】


【学習内容】

Procクラス
・Procクラスとは
・ブロック(復習)
・Proc.newとlambdaの違い
・Procのメリット


【Procクラスとは】


Procとは、ブロックとして記述された手続きを持ち運ぶためのクラスです。
つまり、「ブロックをオブジェクト化したものがProc」ということです。

理解するには「ブロック」とはなんぞやというところから理解を深める必要がありそうです。

【ブロック(復習)】

「ブロック」とは引数のかたまりのことです。
具体的には、do … endもしくは{ … }で囲まれた手続きのかたまりのことを指します。

○timesに対するかたまり

3.times do | i |
 x = i * 2
 p x
end
#=> 0
  2
  4


上記のコードでは、下記の部分がブロックに該当します。
ここのブロックがtimesメソッドに対して「引数」として渡されています。

do | i |
 x = i * 2
 p x
end

○eachに対するかたまり

[ "one", "two", "three" ].each { | n |
 str = "this num is " + n
 p str
}
#=>"this num is one"
"this num is two"
"this num is three"

下記のコードでは、ここの部分がブロックです。


{ | n |
 str = "this num is " + n
 p str
}

○mapに対するかたまり

array = [1, 2, 3, 4]
ret = array.map do |num|
 num + 10
end
p ret
#=>[11, 12, 13, 14]

do |num|
 num + 10
end


このように、ブロックはたしかにコードではありますが、あくまで「引数」として扱われる存在なので、単体では存在することはできません。

ちなみに、each、mapのようなブロック引数を受け取るメソッドのことを「ブロック付きメソッド」と言います。

【Procオブジェクト】

上記のように「ブロック」は単体で存在することはできませんが、Procオブジェクトにブロックを渡すことで、Procオブジェクトとして存在を保つことができます。
保存したProcオブジェクトを使用したいときは「.call」します。

proc = Proc.new {|w| puts w}	#インスタンス作成時にブロックを渡す
def hello(&proc)			#hello関数にprocインスタンスを渡す
 proc.call("hello")		# インスタンスメソッドcallでブロックを使用
end
hello(&proc)		#=>”hello”

hello1 = Proc.new do |name|
 puts “Hello, #{name}.”
end
hello2 = proc do |name|
 puts “Hello, #{name}.”
end

hello1.call(“World”)	#=> Hello,World.
Hello2.call(“Ruby”)	#=> Hello,Ruby.
double = Proc.new do |*args|
 args.map {|i| i * 2}
end

puts double.call(1, 2, 3)	#=> [2, 4, 6]
puts double(2, 3, 4)	#=> [4, 6, 8]


このようにProc.newメソッドにブロックを与えた場合、もしくはprocメソッドにブロックを与えた場合に、そのブロックを保持するProcオブジェクトが作成されます。

そして、ブロックの内容は「callメソッド」で実行できます。

冒頭で「Procとは、ブロックとして記述された手続きを持ち運ぶためのクラス」と表現した意味がわかってきたかと思います。

【yieldの使いかた】


引数として渡されたブロックは、yieldによって実行されます。

def use_block		#ブロックを受け取るメソッドの定義
 yield
end

use_block do		#メソッドの引数にブロックを渡して実行する
puts "Hello, block!"
end

=> "Hello, block!"    	#use_block内で、yieldによって呼び出された。

【yieldより明示的なかきかた】


&をつけることで、引数にブロックが渡されてきたときに、Procオブジェクトに変換します。

def use_block( &block )
 block.call
end

use_block do		#実行
 puts "Hello, block!"
end

=> "Hello, block!"


【lambda(ラムダ式)】


Proc オブジェクトは procとlambda(ラムダ)の2種類に大別されます。

Proc.newやprocメソッドでは、Procオブジェクトを生成します。
lambdaメソッドで作成したProcオブジェクトでは、メソッドの呼び出し方やルールが少し異なってきます。(そこまで深い違いをしりたい方はgoogleまでどうぞ)

○Proc.newで作った場合その1


prc1 = Proc.new do |a, b, c|
 puts [a, b, c]
end

prc1.call(1, 2)		#=>[1, 2, nil]

○lambdaで作った場合その1


prc2 = lambda do |a, b, c|
 puts [a, b, c]
end

prc2.call(1, 2)		#=>エラー

○ここでの両者の違いその1
⇒lambdaは引数のルールが厳しいです。引数の数が間違っているとエラーが出ます。

○Proc.newで作った場合その2

def power_of(n)
 Proc.new do |x|
   return x ** n
 end
end

cube = power_of(3)
puts cube.call(5)		#=>エラー

○lambdaで作った場合その2

def power_of(n)
 lambda do |x|
   return x ** n
 end
end

cube = power_of(3)
puts cube.call(5)		#=>125

○ここでの両者の違いその2
⇒Proc.newの場合、ブロックの中でreturnを使うと、そのブロックを作成したメソッド呼び出しから戻ろうとして、エラーが発生します。
一方、lambdaの場合、ブロックから値を返すときにreturnを使えます。
このreturnはProcオブジェクトのcallメソッドの戻り値になります。


【Procのメリット】

結論から申し上げると、下記の2点が大きなメリットです。
①メソッドを柔軟に拡張できる
②状態を保った関数としての機能(クロージャ)


①メソッドを柔軟に拡張できる
Proc内である程度「処理の土台」を作り、その上でいくつか処理を付け足すことで好みの処理にすることができます。

・処理の土台の定義
この処理は、「数字の10を好きに使える土台」です

def magic_ten_box(after_input, someProc)
 someProc.call(10, after_input)
end

・土台を元に、5を使って足し算をする

sum_proc = Proc.new do |x, y|
 puts x + y
end

magic_ten_box(3, sum_proc)
=> 13


②状態を保った関数としての機能(クロージャ)
呼び出せる関数であると同時に、その関数の変数束縛でもあるオブジェクトのことをクロージャといいます。

変数束縛というのは、引数以外の変数を実行時の環境ではなく自身が定義された環境 (静的スコープ) で解決する、ということを意味しています


n = 1
proc = Proc.new do		#nに1をプラスするproc
 n = n + 1
end
proc.call
=>2
proc.call
=>3
proc.call
=>4

このようにproc内の処理を適用した後の状態を保ったまま、次の処理が行われます。


【所感】

正直、Procは今の僕にはあまり理解できませんでした・・・

体感的には、理解度は全体の半分以下です。

一連の処理を使い回して、あとは好きなようにアレンジどうぞ!のときに使えるクラスとして覚えておきます。

(いつか絶対理解してやる)


この記事が気に入ったらサポートをしてみませんか?