Proc の種類と使い分け 【Ruby】
Effective Ruby 第5章、項目34 「Proc の引数の個数の違いに対応できるようにすることを検討しよう」を読んで、Proc の知識が深まったのでアウトプットしてみます。
まず基本的な知識のおさらいです。
block をメソッド(関数)に渡します。
def hoge(&block) pp block.call("a") end hoge {|x| "#{x} was given." }
=> "a was given."
ブロックはメソッド(関数)側で &
で受け取ると Proc オブジェクトになって、.call で呼べます。
block.call("a")
のところは yield("a")
でもOK。
対して、lambda は その場でProc オブジェクトになります。つまり、その場で呼べます。
irb(main):002:0> ->(x) {"#{x} was given."}.call("a")
=> "a was given."
逆にメソッドに対して、lambda を渡すことはできません。
def hoge(&block) pp block.call("a") end my_lambda = ->(x) {"#{x} was given."} hoge my_lambda
=> `hoge': wrong number of arguments (given 1, expected 0) (ArgumentError)
当然ながら、その場でProcオブジェクトになり、変数に代入できているので、それをメソッドに渡すとフツーに引数としてみなされます。
そしたら、引数受取部の &
をとってみましょう。
def hoge(block) pp block.call("a") end my_lambda = ->(x) {"#{x} was given."} hoge my_lambda
=> "a was given."
いけましたね。
んで、次に「lambda はblock として渡せないの?(メソッド引数受取側で &block
のまま渡す)」ってなります。
これはメソッド呼び出し時に &
をつければよいです。
def hoge(&block) pp block.call("a") end my_lambda = ->(x) {"#{x} was given."} hoge &my_lambda
=> "a was given."
じゃあ、ブロックをメソッド側で &
で受け取ったもの(block がProc オブジェクトになったもの)と、lambda は同じものなのかって気になりますよね。実際にみてみます。
def hoge(&block) pp block end hoge {|x| "#{x} was given." }
=> #<Proc:0x00007fedd70c2e30@(irb):12>
pp ->(x) {"#{x} was given."}
=> #<Proc:0x00007fedd7101388@(irb):2 (lambda)>
lambda のほうは (lambda)
となっていますね。
これの違いは Effective Ruby では、
block(をProcオブジェクト化したもの) => 弱いProc lambda => 強いProc
と言われていて、その違いは
引数の個数を厳密にチェックするかどうか
という所です。
lambda のほうはブロックを実行する際に、引数の個数が違っていれば、ArgumentError
例外を吐きます。
さきほどのコードで見てみましょう。
まず、blockです。
require "pp" def hoge(&block) pp block.call("a") end hoge {|x, y| "#{x} and #{y} was given." }
=> "a and was given."
まぁフツーに nil になって string にキャストされてます。
次に lambda
pp ->(x, y) {"#{x} and #{y} was given." }.call("a")
=>
proc.rb:26:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError) from proc.rb:26:in `<main>
となりました。
ここまでの話を踏まえて、どう使い分けるかって所ですが、Effective Ruby では、「強いProc を渡したい状況で、実行時(.call時)に引数の個数が違っていたら例外が発生するから、そういう時は、Proc#arity
メソッド使おうぜ」 って言っています。
強いProc を渡したい状況
というのは、Effective Ruby では、メソッドを渡すケースを説明されていました。さきほどの lambda を hoge &my_lambda
としたケースを思い出してください。アレ実はメソッドオブジェクトでもOKなんです。
例えば、以下のようにすると
def hoge(&block) pp block.call("a") end hoge &"aiueo".method(:include?)
=> true
となります。
※ こんな実用例はないんですが、仕様だけ説明したいと思います
仕組みとしては、メソッドオブジェクトをProc に変換すると lambda(強いProc) になるということです。
def hoge(&block) pp block end hoge &"aiueo".method(:include?)
=> #<Proc:0x00007faf708c4070 (lambda)>
つまり、ここで、block.call("a", "b")
となっていたら、例外になります。
このような、「メソッドオブジェクト(強いProc)を渡したいけど、メソッド側で .call
するときにいくつ引数を渡すかを決めたくない」ときにProc#arity
メソッドを使います。
def hoge(&block) pp "block引数の個数 : #{block.arity}" if block.arity == 1 pp block.call("a") end if block.arity == 2 pp block.call("a", "b") end end hoge &"aiueo".method(:include?)
=>
"block引数の個数 : 1" true
こんな感じです。
まとめると、
- Proc オブジェクトは
強いProc(lambda, メソッドオブジェクトをProcに変換したもの)
と弱いProc(block を Procに変換したもの)
があり、強いProcは引数の個数が違かったら ArgumentError例外が発生する。 - Proc#arity メソッドで Procオブジェクトが期待する引数の個数がわかる
ということですな。
Proc#arity の具体例はもっと実用的な例が Effective Ruby に載っているので興味あるかたは是非みてみてください。