Procクラス
- ブロックをオブジェクトとして扱いたい時に使用するクラス
- ブロック
- メソッドの引数として渡すことが出来る処理のまとまり
-
do .. end
,{ .. }
- ブロック
- Procは、クロージャに相当する
i = 100
pr = Proc.new { i += 1000 } # proc { i += 1000 } も同様
p pr.call #=> 1100
p i #=> 1100
p pr.call #=> 2100
p i #=> 2100
クロージャとは
クロージャとは関数とその関数が定義されている状態をセットにしたもの
・記述の省略が可能
・処理をまとめられる
・カプセル化ができる
クロージャのイメージ
def add_num n
yield + n
end
i = 10
p add_num(1) { i += 100 } #=> 111
p i #=> 110
p add_num(1) { i += 1000 } #=> 1111
p i #=> 1110
上の例だと、
-
#add_num
の引数に1
を渡す -
#add_num
内のyield
がブロックの処理を呼び出す - ブロックの中で、ローカル変数
i
を更新する - 更新された変数
i
とn
の加算を行っている(代入はしていない) - ブロック付きメソッドを呼び出す度に、外部の変数である
i
が更新されている
外部のローカル変数を捉え、その変数に対してアクセス・変更が可能
遅延評価したい場面などで使いやすい
ブロックをProcに変換して受け取りたい場合
def calc_result(x, &block)
# p block.class #=> Proc
result = block.call(x)
puts "Result: #{result}"
end
i = 100
# 加算のブロックを渡す
calc_result(900) { |n| i += n } #=> Result: 1000
# 減算のブロックを渡す
calc_result(300) { |n| i -= n } #=> Result: 700
p i #=> 700
最後の引数に「&」を付けることで、ブロックをProcオブジェクトとして受け取る
#block_given?
def calc_result(x, y, proc_object = nil, &block)
if proc_object
result = proc_object.call(x, y)
puts "Using Proc: #{result}"
elsif block_given?
result = yield(x, y)
puts "Using Block: #{result}"
else
puts "No Proc or Block specified."
end
end
# Procオブジェクトを定義
addition = Proc.new { |a, b| a + b }
# Procオブジェクトを指定
calc_result(100, 200, addition) #=> Using Proc: 300
# ブロックを指定
calc_result(100, 200) { |a, b| a - b } #=> Using Block: -100
# Proc・ブロックのいずれも指定しない
calc_result(100, 200) #=> No Proc or Block specified.
Proc #<<
#>>
self と引数を合成した Proc を返します。
# Procオブジェクト1: 与えられた引数に1を加える
add_one = Proc.new { |x| x + 1 }
# Procオブジェクト2: 与えられた引数を2倍する
multiply_by_two = Proc.new { |x| x * 2 }
# 左合成 (Proc#<<): 最初にmultiply_by_twoを適用し、その結果をadd_oneに渡して実行する
composed_proc_left = add_one << multiply_by_two
# 右合成 (Proc#>>): 最初にadd_oneを適用し、その結果をmultiply_by_twoに渡して実行する
composed_proc_right = add_one >> multiply_by_two
# 引数用の値
num = 3
# 左合成の結果
result_left = composed_proc_left.call(num) # (3 * 2) + 1
puts "Left Composition Result: #{result_left}" #=> Left Composition Result: 7
# 右合成の結果
result_right = composed_proc_right.call(num) # (3 + 1) * 2
puts "Right Composition Result: #{result_right}" #=> Right Composition Result: 8
lambda
l = lambda { |a, b| a - b }
p l.class #=> Proc
p l.lambda? #=> true
p l.call(10, 3) #=> 7
# Ruby1.9 ~ は、以下のようにも書くことが出来る
l = ->(a, b) { a - b }
p l.call(10, 3) #=> 7
proc
と lambda
の振る舞いの違い
引数の扱い
lambda
では、引数の個数が違うと ArgumentError が発生する
pr = ->(a, b) { p a, b }
pr.call(10) #=> sample.rb:1:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)
Proc.new
, proc
では発生しない
pr = proc { |a, b| p a, b }
pr.call(10)
#=> 10
# nil
ジャンプ構文の挙動
def test_proc
pr = Proc.new { return :from_proc }
pr.call
return :from_method
end
def test_lambda
l = lambda { return :from_lambda }
l.call
return :from_method
end
def test_block
tap { return :from_block }
return :from_method
end
p test_proc #=> :from_proc
p test_lambda #=> :from_method
p test_block #=> :from_block
Proc.new は、ブロック内でのreturnと共に、呼び出し元のメソッドも抜けている。
lambdaは、ブロック内でreturnをするだけで、呼び出し元の後続処理は継続する。
return | next | break | |
---|---|---|---|
Proc.new | メソッドを抜ける | 手続きオブジェクトを抜ける | 例外が発生する |
proc | メソッドを抜ける | 手続きオブジェクトを抜ける | 例外が発生する |
lambda | 手続きオブジェクトを抜ける | 手続きオブジェクトを抜ける | 手続きオブジェクトを抜ける |
イテレータ | メソッドを抜ける | 手続きオブジェクトを抜ける | 手続きオブジェクトを抜ける |
Proc
を生成したメソッドから脱出した後、手続きオブジェクトからの return
, break
は LocalJumpError が発生する。
def foo
Proc.new { return :from_proc }
end
p foo.call #=> sample.rb:2:in `block in foo': unexpected return (LocalJumpError)
lambda
では発生しない
def foo
lambda { return :from_lambda }
end
p foo.call #=> :from_lambda