:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

Proc

Ruby技術者認定試験の書籍写経メモ

Proc

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オブジェクトを渡し、ブロックとして受け取る

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オブジェクトとして受け取る

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"
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オブジェクトとして受け取る

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"

ブロックを渡し、ブロックとして受け取る

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

作り方

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 で生成した手続きオブジェクトは引数への多重代入のように振る舞うため、エラーにならない

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が発生する

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 を使うとその外側を抜けようとするため、メソッドやブロックの外側ではエラーが発生する

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