RubyにおけるブロックとProc
ブロック
基本
ブロック: メソッドを呼び出すときに記述できる
yield: メソッド内部でブロック内部の処理を呼び出すときに使う式
code:rb
# これはメソッド定義
def foo(a)
a + yield # a + 「fooメソッドの呼び出し時に渡されたブロックの実行結果」
end
# メソッド呼び出し
foo(10){ 5 } # { 5 }がブロック。fooメソッドの引数として 10 とブロック { 5 } を渡した、ということ
# => 15
ブロックは新しいスコープをつくる
ブロック内で初期化された変数はブロック外で参照できない
code:rb
foo(100){ x = 30 } # さっきの続きでfooメソッドを使う。ブロック { x = 30 } を渡す。
# => 130
x # undefined local variable or method `x' for main:Object (NameError)
ブロックの外で初期化済みの変数はブロック内でも参照できる
code:rb
y = 1
foo(100){ y += 10 } # つまりブロック { 11 } を渡したのと同じ。
# => 111
y # y は 10 加算されている
# => 11
Proc
ブロックをオブジェクトとして扱えるようにしたもの
ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。
手続き(Procedure)の先頭4文字を取って Proc というクラス名(だと思う)
手続きオブジェクトの使い方のサンプル
code:rb
# メソッドbarを定義する: 与えられた文字列に対して与えられたブロックの処理結果をn個つなげるメソッド
def bar(str, n)
yield(str) * n
end
# ブロックを渡してメソッドを呼び出す(ここまではブロックの使い方の話)
bar('ruby', 3){|x| x.capitalize} # => "RubyRubyRuby"
# 与えられた文字列を加工する手続きをオブジェクトとして定義する
# 手続きオブジェクトの処理は call で呼び出せる
upcaser.call('abc') # => "ABC"
reverser.call('abc') # => "cba"
# barメソッドに、手続きオブジェクトをブロックとして渡したい
# → 手続きオブジェクトの先頭に & をつけると、ブロックに変換して渡すことができる
bar('ruby', 10, &upcaser) # => "RUBYRUBYRUBYRUBYRUBYRUBYRUBYRUBYRUBYRUBY"
bar('ruby', 5, &reverser) # => "yburyburyburyburybur"
メソッド側にてブロックを手続きオブジェクトとして受け取りたい場合
code:rb
# 引数に & をつけた名前をつかってメソッドを定義する
def baz(str, n, &str_processor)
str_processor.call(str) * n
end
# ブロックを str_processor として受け取れる
baz('ruby', 3){|x| x.capitalize} # => "RubyRubyRuby"
# 試しに、引数に & をつけずに定義してみる
def hoge(str, n, str_processor)
str_processor.call(str) * n
end
# ブロックを受け取れない(というより、第3引数を渡していないのでArgumentErrorになる。それはそう)
hoge('ruby', 3){|x| x.capitalize}
# => `hoge': wrong number of arguments (given 2, expected 3) (ArgumentError)
# 純粋に手続きオブジェクトを定義して第3引数に渡してあげれば良い
hoge('ruby', 3, capitalizer) # => "RubyRubyRuby"
lambdaとは
code:rb
# 記法
# callで処理を呼び出す
lambda_syntax1.call('hey') # => "hey"
lambda_syntax2.call('hey') # => "hey"
Proc.new と lambda の比較
どちらもProcクラスのオブジェクトである
code:rb
# lambda?メソッドでlambdaかどうかを判定できる
upcaser_by_proc_new.lambda? # => false
upcaser_by_lambda.lambda? # => true
引数の扱いが異なる(lambdaのほうが厳密)
code:rb
y.call(1, 2) # => wrong number of arguments (given 2, expected 3) (ArgumentError)
手続きオブジェクト内で return または break したときの挙動が異なる
table:比較
return break
Proc.new 呼び出し元スコープを抜ける 例外が発生する
lambda 手続きオブジェクトを抜ける 手続きオブジェクトを抜ける
returnについて
code:rb
def test_proc_new
f = Proc.new { return :from_proc_new }
f.call
return :from_method
end
def test_lambda
f = lambda { return :from_lambda }
f.call
return :from_method
end
# Proc.newの内部のreturnによって、呼び出し元(test_proc_newメソッド)のスコープを抜けている
test_proc_new # => :from_proc_new
# lambdaの内部のreturnではlambdaの処理を抜けるだけで、呼び出し元のスコープは抜けない
test_lambda # => :from_method
もうちょい別の例も
code:rb
def test_proc_new(str)
upcaser = Proc.new do |x|
return '' unless x.is_a?(String)
x.upcase
end
end
def test_lambda(str)
upcaser = lambda do |x|
return '' unless x.is_a?(String)
x.upcase
end
end
# 引数に文字列を渡した場合は同じ結果になる
test_proc_new('abc') # => "abc: ABC"
test_lambda('abc') # => "abc: ABC"
# 引数にnilを渡した場合は異なる結果になる
test_proc_new(nil) # => "" (Proc.newの内部で '' をreturnしてるので、メソッドも '' を返す)
test_lambda(nil) # => ": "
breakについて
code:rb
def test_proc_new
f = Proc.new { break :from_proc_new }
f.call
return :from_method
end
def test_lambda
f = lambda { break :from_lambda }
f.call
return :from_method
end
test_proc_new
# => `block in test_proc_new': break from proc-closure (LocalJumpError)
test_lambda
# => :from_method