これがあなたの麺を竹で割る質問です。
class_eval
クラスをレシーバーとして使用して評価されるブロックでは、メソッド宣言と定数宣言に違いがあるようです。不一致を示すコードは次のとおりです。
module X
def save_block(&block)
@block = block
end
end
module Y
extend X
save_block do
SOME_CONSTANT = 1
def meth
"a method"
end
end
def self.included(m)
m.class_eval &@block
end
end
class Z
include Y
end
class W
include Y
end
少なくともRuby1.9.3でこのコードを実行すると、次のエラーが発生します。
warning: already initialized constant SOME_CONSTANT
私はそれを予期していませんでした。最初にどこmeth
にあるかを考えてみましょう。
Z.instance_methods(false)
=> [:meth]
W.instance_methods(false)
=> [:meth]
そしてY
、インスタンスメソッドはありません:
Y.instance_methods(false)
=> []
ブロックはのレシーバーとして実行Z
されるため、これは理にかなっています。ただし、定数が異なるため、次のようなエラーメッセージが表示されます。W
class_eval
Y.constants(false)
=> [:SOME_CONSTANT]
Z.constants(false)
=> []
W.constants(false)
=> []
ここで、定数はY
(何らかの奇妙な理由で)で定義されることになります。したがって、ブロックが2回実行されるときに、定数SOME_CONSTANT
はすでに定義されています。
啓蒙をとても楽しみにしています。
更新(2012/11/19):
@phoetが以下で指摘しているように(彼の回答に対する私のコメントへの応答)、定数はブロックの字句スコープ、つまりブロックが最初に定義されたスコープで定義されます。私の例では、これはYになります。