Ruby のブロックと Proc と lambda についてまとめました。
Procオブジェクトの生成
Proc オブジェクトを生成するにはProc.new
Kernel.#proc
Kernel.#lambda
にブロックを渡します。またはlambda
の別の記法でもあるアロー演算子が使えます。
pobj = Proc.new { |x| x + 1 } # => #<Proc:0x007fb9ca80fda0@(irb):101> pobj.call(1) # => 2 pobj = proc { |x| x + 2 } # => #<Proc:0x007fb9cb095678@(irb):103> pobj.call(1) # => 3 pobj = lambda { |x| x + 3 } # => #<Proc:0x007fb9cb05e970@(irb):104 (lambda)> pobj.call(1) # => 4 pobj = ->(x) { x + 4 } # => #<Proc:0x007f9aac8150f8@(irb):105 (lambda)> pobj.call(1) # => 5
class Proc (Ruby 2.4.0)
module function Kernel.#proc Kernel.#lambda (Ruby 2.4.0)
ブロックを受け取るメソッド
ブロックはメソッドに渡す無名引数のようなものです。メソッドの仲でyield
を使用すると受け取ったブロックが実行されます。yield
があるのにブロックなしで呼び出すと例外で失敗します。
def my_method(arg) yield(arg) end my_method('Bob') {|name| "Hello, #{name}!"} # => "Hello, Bob!" my_method('Bob') LocalJumpError: no block given (yield) from (irb):21:in `my_method' from (irb):23
ブロックを受け取ったかどうかはblock_given?
で判定できます。
def my_method block_given? end my_method { 'hoge' } # => true my_method # => false
ブロックを引数で明示的に受け取ることも可能です。その場合は引数の最後で名前の前に&
修飾を付けます。&
を付けると「メソッドに渡されたブロックを受け取ってProc
に変換する」という意味になります。
引数で明示した場合でも省略した場合でもブロックは1つしか受け取れません。
def my_method(arg, &my_block) yield(arg) end my_method('Ben') {|name| "Hello, #{name}!"} # => "Hello, Ben!"
明示的にブロックを受け取る意味としては、ブロックをProc
に変換したい時、または他のメソッドにブロックを渡したいときです。
# ブロックをProcに変換 def my_method(&my_block) my_block end obj = my_method {|x| x + 1} # => #<Proc:0x007f9aac866430@(irb):37> obj.call(1) # => 2 # 他のメソッドにブロックを渡す def do_method(arg) yield(arg) end def my_method(arg, &my_block) do_method(arg, &my_block) end my_method('Nick') {|name| "Hello, #{name}!"} # => "Hello, Nick!"
といいつつブロックをProc
に変換したい時でも明示的に引数で受け取らなくても変換できます。Proc.new
またはlambda
でブロックを指定しなければ、メソッドを呼び出しにブロックを伴うときに、それをProc
オブジェクトとして生成して返すためです。
def my_method p = Proc.new p.call(2) end my_method {|v| v**v } # => 4
ただしlambda
の場合は Warning がでます。意味的には「ブロック無しでProcオブジェクトを生成しようとしました」です。あまり分かりづらいコードになるのであまり使わない方がよさそうです。
def my_method p = lambda p.call(3) end my_method {|v| v**v } # => 27 (irb):86: warning: tried to create Proc object without a block
Proc や lambda はオブジェクトなのでそのまま引数で受け取れます。
def my_method(pobj) pobj.call(2) end my_proc = proc { |v| v**v } my_method(my_proc) # => 4
メソッド呼び出し(super・ブロック付き・yield)
Rubyで使われる記号の意味(正規表現の複雑な記号は除く)
ブロックとProcの変換
そもそもブロック自体はオブジェクトではなく、ブロックがオブジェクトになったものがProc
です。
&
修飾を付けるとProc
からブロックに変換されます。引数&
を付けなければProc
のままです。
&
を付けなければというのは上述したブロックをProc
に変換するで説明した引数で明示的に受け取った場合のことです。
def convert_proc(&my_block) # &my_blockという変数が生成されるわけではなくmy_blockというProcで受け取る do_method(&my_block) # 他のメソッドにブロックとして渡したい場合は&を付ける my_block # &を付けなければProcのまま end
Proc
を生成しておいて&
を付けてブロックを受け取るメソッドに渡すことも可能です。
ary = [1,2,3,4,5] my_proc = proc { |v| v**v } ary.map &my_proc # => [1, 4, 27, 256, 3125]
あらかじめProc
を生成しなくても&
を付けることでブロックに変換できます。
ary = [1,2,3,4,5] ary.map &->(v) { v**v } # => [1, 4, 27, 256, 3125]
Procとlambdaの違い
lambda
で生成した Proc は通常のものと違います。主な違いは引数のチェックとreturn
の挙動です。lambda の方がよりメソッドに近い働きをします。
引数チェック
引数のチェックに関しては lambda の場合、引数の数が違うと ArgumentError になりますが、Proc の場合は渡す引数が多いと、先頭から必要な数だけ取って後は無視し、少ないと足りない部分に nil を割り当てるため多重代入に近い扱い方をします。
l = lambda {|a, b| [a, b]} l.call(1,2) # => [1, 2] l.call(1,2,3) ArgumentError: wrong number of arguments (given 3, expected 2) from (irb):158:in `block in irb_binding' p = Proc.new {|a, b| [a, b]} p.call(1,2,3) # => [1, 2] p.call(1) # => [1, nil]
returnとbreakとnextの挙動
lambda の場合は return を使うと lambda 内から外に戻ります。
def lambda_method l = lambda { return :foo } l.call return nil end p lambda_method # => nil
Proc の場合は Proc 内から戻るのではなく、Proc が定義されたスコープから戻ります。
def proc_method pobj = proc { return :bar } pobj.call return nil end p proc_method # => :bar
そのためトップレベルで定義した場合はスコープから戻れないため失敗します。
def proc_method(pobj) pobj.call end pobj = proc { return :bar } proc_method(pobj) LocalJumpError: unexpected return from (irb):269:in `block in irb_binding' from (irb):267:in `proc_method'
Proc でbreak
をを使うと例外が発生します。 lambda はreturn
と同じ挙動です。
def proc_method pobj = proc { break } pobj.call return nil end p proc_method LocalJumpError: break from proc-closure from (irb):276:in `block in proc_method' from (irb):277:in `proc_method'
next
は lambda と Proc で同じで lambda 内から外に戻ります。
def proc_method pobj = proc { return :bar } pobj.call return nil end p proc_method # => :bar
結論として lambda の場合はreturn
break
next
で同じ挙動をしますが、Proc でのreturn
break
だけが定義されたスコープを抜けるという変わった挙動になるので注意が必要です。
ブロックのスコープ
ブロック (Procとlambdaも) を定義するとその時点でそのスコープにある束縛を取得します。そしてブロックをメソッドに渡した時はその束縛も一緒に渡ることになります。
def block_method x = 'inner' yield # => "outer" end x = 'outer' block_method { puts x x = 'foo' } puts x # => 'foo'
逆にブロックの中で定義した変数などはブロックが終了した時点で消えます。
def block_method yield end block_method { y = 'bar' } puts y NameError: undefined local variable or method `y' for main:Object