lamndaとProcをclass_evalの引数として渡すときの挙動の違い

気になって調べたのでメモがてら書く。

調べたこと

何が気になったのかというとlambdaで以下のように書くとArgumentErrorになることだ。

foo = -> { def bar; puts 'baz'; end }
Piyo = Class.new
Piyo.class_eval(&foo)
#=> ArgumentError: wrong number of arguments (given 1, expected 0)

一方Procで書くと同じ処理が問題なく通るようになる。

foo = proc { def bar; puts 'baz'; end }
Piyo = Class.new
Piyo.class_eval(&foo)
#=> :bar

Piyo.new.bar
baz
#=> nil

調査結果

こちらにもう答え書いてあったんだが、かいつまんで説明する。

この理由は、

  1. lambdaでの引数の扱いが厳密なこと
  2. class_eval(module_eval)の仕様

によるものぽい。

1に関しては僕も元々知ってたし、以下のようにるりまにも明示されている。

lambda のほうがより厳密です。引数の数が違っていると(メソッドのように)エラーになります。 Proc.new は引数を多重代入に近い扱い方をします。

つまり、lambdaとProcの引数の扱いの違いによって、上記の例の結果がもたらされたわけ。

ではどこから引数が渡されたのかというとそれは2のclass_evalの仕様による所らしい。

上のドキュメントをよく見ると、

module_eval(expr, fname = “(eval)”, lineno = 1) -> object[permalink][rdoc]
module_eval {|mod| … } -> object
class_eval(expr, fname = “(eval)”, lineno = 1) -> object
class_eval {|mod| … } -> object

とある。

原因はこいつ -> class_eval {|mod| ... } -> object

つまりclass_evalの引数にブロックを渡すと、そのブロックの引数にmodule自体が渡されるぽい。

Piyo.class_eval do |mod|
   p mod
end
Piyo
=> Piyo

だからlambdaでは落ちたということで、こう書いたらちゃんと動いた。

foo = ->(_mod) { def bar; puts 'baz'; end }
Piyo = Class.new
Piyo.class_eval(&foo)
#=> :bar

調べてみるとなんてこと無いけど、実装しててなんで?てなりました。

参考