Proc
Proc
- ブロックを実行時のローカル変数などのコンテキストと共にオブジェクト化した手続きオブジェクトを扱うクラス
- 無名関数のように使うことができる
- ProcオブジェクトはProcクラスのコンストラクタにブロックを指定すると生成
- Procオブジェクトのcallメソッドを呼び出し実行する
- たとえば遅延評価、関数型プログラミングでも使う?
RuboCop先生は手続きオブジェクトはこうやって作れって言ってくるんだよね、実際にはこう書いた方がいいんかね?
# bad
pr = Proc.new { |n| puts n }
# good
pr = proc { |n| puts n }
手続きオブジェクトを生成、実行する
new して call
pr = Proc.new { p 'hage-!' }
pr.call
#=> "hage-!"
手続きオブジェクトを生成、実行する 引数も取れる
pr = Proc.new { |a| a + 1 }
pr.call(3)
#=> 4
生成された手続きオブジェクトの引数の数は、arityメソッドで取得できます
pr = Proc.new { |a| a + 1 }
pr.arity
=> 1
pr = Proc.new { |a, b| a + b }
pr.arity
=> 2
オブジェクト生成時のコンテキストを保持しているため、ローカル変数の値などは実行時の状況に応じて変化する
procはaを参照できているのに(=>70)、
a = 30
b = 40
pr = Proc.new { a + b }
def hage(pr)
pr.call
end
hage(pr)
=> 70
当然だが、hageメソッド内でaは参照することができない(NameError)
a = 30
b = 40
def hage
p a
end
hage
=> NameError: undefined local variable or method 'a'
proc定義時に存在していない[a, b]への参照は出来ないっぽい。ホイストしてくれないって事でおk?
pr = Proc.new { a + b }
a = 30
b = 40
pr.call
=> undefined local variable or method 'a'
procは[a, b]への参照を保持しているので、再実行時に[a, b]の値が変わっていても当然ながら値に追随して実行される
a = 30
b = 40
pr = Proc.new { a + b }
pr.call
#=> 70
a = 50
b = 60
pr.call
#=> 110
ブロック付きメソッドへの引数として利用する
- ブロック付きメソッドに手続きオブジェクトを渡すことも可能
- 変数名の前に&を付けて渡す
pr = Proc.new { |i| p i }
3.times(&pr)
0
1
2
=> 3
pr = Proc.new { |key, value| p "#{key}#{value}" }
{a: 1, b: 2, c: 3}.each(&pr)
"a1"
"b2"
"c3"
=> {:a=>1, :b=>2, :c=>3}
ただし、後述 しますが break return next などのジャンプ構文で振る舞いが変わってくるので注意
手続きオブジェクトの中での処理の中断
手続きオブジェクトの中で処理を中断して呼び出し元へ値を戻すには breakでもreturnでもなく、nextを使用します
pr = Proc.new do
next
p 'end'
end
pr.call
=> nil
pr = Proc.new do
next 'next' # 戻り値を指定
p 'end'
end
pr.call
=> "next"
メソッドへ手続きを渡す、メソッドで手続きを受け取る
procオブジェクトを渡し、ブロックとして受け取る | |
procオブジェクトを渡し、procオブジェクトとして受け取る | |
ブロックを渡し、procオブジェクトとして受け取る | |
ブロックを渡し、ブロックとして受け取る | あ、これは平常時にyieldやってる奴だわ |
- メソッド呼び出し時の変数で、procオブジェクトに&を付けて呼び出すと「このprocはブロックとして渡すわ、yieldヨロ」という事になる
- ブロックが渡されたので
- yieldが可能になる
- block_given? が true になる
- ブロックが渡されたので
- 呼び出されるメソッドで仮引数に&を付けた変数で受けとると「渡されたブロックをprocオブジェクトとして扱うわ」という事になる
- procオブジェクトが手に入ったので
- callが可能になる
- ブロックが渡されたので
- yieldが可能になる
- block_given? が true になる
- procオブジェクトが手に入ったので
procオブジェクトを渡し、ブロックとして受け取る
- &を付けた最後の変数としてprocをメソッドに渡すと、ブロックを渡した事になる
- 呼び出されたメソッドでは何の準備もしていないので普通にブロックをyieldで実行する事になる
- procオブジェクトへの参照は普通には出来なくなる
- ブロックが渡されたので block_given? は true
def moko(a)
p block_given?
p a
p yield
end
pr = Proc.new do
'I am Proc.new'
end
moko('hage', &pr)
true
"hage"
"I am Proc.new"
procオブジェクトを渡し、procオブジェクトとして受け取る
- procオブジェクトを普通にオブジェクトのまま変数としてメソッドに渡す
- 呼び出されたメソッドでは何の準備もしていないので普通にcallで実行する事になる
- ブロックが渡されていないのでので yieldは出来ない。block_given? は false
def moko(a, pr)
p block_given?
p a
p pr.call
end
pr = Proc.new do
'I am Proc.new'
end
moko('hage', pr)
false
"hage"
"I am Proc.new"
- &を付けた最後の変数としてprocをメソッドに渡すと、ブロックを渡した事になる
- 呼び出されたメソッドでは最後の変数の頭に&を付けて、渡されたブロックをprocオブジェクトとして受け取る
- 受け取ったprocオブジェクトへのcallが可能
- ブロックが渡されたので yieldが可能。block_given? は true
def moko(a, &pr)
p block_given?
p a
p pr.call
p yield
end
pr = Proc.new do
'I am Proc.new'
end
moko('hage', &pr)
true
"hage"
"I am Proc.new"
"I am Proc.new"
ブロックを渡し、procオブジェクトとして受け取る
- 呼び出されたメソッドでは最後の変数の頭に&を付けて、渡されたブロックをprocオブジェクトとして受け取る
- 受け取ったprocオブジェクトへのcallが可能
- ブロックが渡されたので yieldが可能。block_given? は true
def moko(a, &pr)
p block_given?
p a
p pr.call
p yield
end
moko('hage') do
'I am block'
end
true
"hage"
"I am block"
"I am block"
ブロックを渡し、ブロックとして受け取る
- 特に新しい事ではない
- ブロックをブロックのままメソッドに渡す
- ブロックが渡されたので yieldが可能。block_given? は true
def moko(a)
p block_given?
p a
p yield
end
moko('hage') do
'I am block'
end
true
"hage"
"I am block"
Procクラス以外の手続きオブジェクト
Kernel#lambda |
Kernel#proc |
- 殆どProcオブジェクトと同じような振る舞いをするが、いくつかの場面で違いがある
- 合言葉は「lambdaはメソッドっぽい振る舞いをする」
作り方
l = lambda { |a| a + 2 }
=> #<Proc:0x007f8dbf9273c8@(irb):22 (lambda)>
l.call(1)
=> 3
pr = proc { |a| a + 2 }
=> #<Proc:0x007f8dbf875a88@(irb):24>
pr.call(1)
=> 3
手続きオブジェクトにおける引数の数のチェック
lambda proc メソッドで作った手続きオブジェクトは行数の数が違うとArgumentErrorを発生させるが、 Proc.new で生成した手続きオブジェクトは引数への多重代入のように振る舞うため、エラーにならない
- との事だったが、Kernel#proc は Proc.new と同じ動作、もしくはいつの間にか同じ動作になっていた、のではないかと思うので、正しくは下記なんじゃないかなー
lambda メソッドで作った手続きオブジェクトは行数の数が違うとArgumentErrorを発生させるが、 Proc.new, procメソッド で生成した手続きオブジェクトは引数への多重代入のように振る舞うため、エラーにならない
l = lambda { |a| (a || 0) + 2 }
l.call
=> ArgumentError: wrong number of arguments (given 0, expected 1)
l.call(1, 2)
=> ArgumentError: wrong number of arguments (given 2, expected 1)
ArgumentError が発生しない(´=ω=`) やっぱ proc は Proc.new と同じ挙動なんじゃねえかなぁ
pr = proc { |a| (a || 0) + 2 }
pr.call
=> 2
pr.call(1, 2)
=> 3
pr = Proc.new { |a| (a || 0) + 2 }
pr.call
=> 2
pr.call(1, 2)
=> 3
手続きオブジェクトにおけるジャンプ構文
手続きオブジェクトにおけるジャンプ構文 - break
lambda, procメソッドで作った手続きオブジェクト内で break を使うとその手続オブジェクトを抜けるが Proc.new で作った手続きオブジェクト内で break を使うとLocalJumpErrorが発生する
- との事だったが、Kernel#proc は Proc.new と同じ動作、もしくはいつの間にか同じ動作になっていた、のではないかと思うので、正しくは下記なんじゃないかなー
lambda メソッドで作った手続きオブジェクト内で break を使うとその手続オブジェクトを抜けるが Proc.new, proc で作った手続きオブジェクト内で break を使うとLocalJumpErrorが発生する
pr = Proc.new do
break
p 'I am Proc.new'
end
pr.call
=> LocalJumpError: break from proc-closure
pr = proc do
break
p 'I am proc'
end
pr.call
=> LocalJumpError: break from proc-closure
l = lambda do
break
p 'I am lambda'
end
l.call
=> nil
ただし、ブロックに渡した時にはどの手続オブジェクトでもLocalJumpErrorとなる
- との事だったが、正しくは下記なんじゃないかなー
ブロックに渡した時でも、callした時と動作は一緒
pr = Proc.new do |a|
break
p 'I am Proc.new'
end
1.times(&pr)
=> LocalJumpError: break from proc-closure
pr = proc do |a|
break
p 'I am proc'
end
1.times(&pr)
=> LocalJumpError: break from proc-closure
l = lambda do |a|
break
p 'I am lambda'
end
1.times(&l)
=> 1
手続きオブジェクトにおけるジャンプ構文 - return
lambda, procメソッドで作った手続きオブジェクト内で return を使うとその手続オブジェクトを抜けるが Proc.new で作った手続きオブジェクト内で return を使うとその外側を抜けようとするため、メソッドやブロックの外側ではエラーが発生する
- との事だったが、Kernel#proc は Proc.new と同じ動作、もしくはいつの間にか同じ動作になっていた、のではないかと思うので、正しくは下記なんじゃないかなー
lambda メソッドで作った手続きオブジェクト内で return を使うとその手続オブジェクトを抜けるが Proc.new, proc で作った手続きオブジェクト内で return を使うとエラーが発生する
pr = Proc.new do
return
p 'I am Proc.new'
end
pr.call
=> LocalJumpError: unexpected return
pr = Proc.new do |a|
return
p 'I am Proc.new'
end
1.times(&pr)
=> LocalJumpError: unexpected return
pr = proc do
return
p 'I am proc'
end
pr.call
=> LocalJumpError: unexpected return
pr = proc do |a|
return
p 'I am proc'
end
1.times(&pr)
=> LocalJumpError: unexpected return
l = lambda do
return
p 'I am lambda'
end
l.call
=> nil
l = lambda do |a|
return
p 'I am lambda'
end
1.times(&l)
=> 1