From: Minero Aoki Date: 2002-09-18T17:03:28+09:00 Subject: [ruby-dev:18314] class nest in module_eval あおきです。 CVS 最新で次のような挙動が起こります。 [テスト1] ~/t/rhg % cat t class A class B class T puts "A::B::T = #{self.id}" end ::A.module_eval { class T # A::B::T を上書き定義する puts "A::B/A::T = #{self.id}" end } end end puts "A::B::T = #{A::B::T.name} (#{A::B::T.id})" ~/t/rhg % ruby t A::B::T = 359000264 t:8: warning: already initialized constant T A::B/A::T = 359000184 A::B::T = A::T (359000184) # A::T は undefined const [テスト2] ~/t/rhg % cat t2 class A class T puts "A::T = #{self.id}" end class B class T puts "A::B::T = #{self.id}" end ::A.module_eval { class T # こちらは A::T に追加定義する puts "A::B/A::T = #{self.id}" end } end end puts "A::T = #{A::T.name} (#{A::T.id})" puts "A::B::T = #{A::B::T.name} (#{A::B::T.id})" ~/t/rhg % ruby t2 A::T = 358999854 A::B::T = 358999744 A::B/A::T = 358999854 A::T = A::T (358999854) A::B::T = A::B::T (358999744) つまり、 1. A::T が定義されているかどうかで module_eval 中に 定義したクラスのネスト先が変わる 2. 上書きするか追加定義するかの違いがある 3. テスト 1 で、実際には A::B::T であるクラスの名前 が A::T になる というあたりが変なわけです。3. については純粋にバグだったよう なので、残る 1. 2. がこれでいいのか、変えるとしたらどう変える のか考えないといけません。 [考察] そもそもこういう挙動になる原因は、rb_eval:NODE_CLASS で定義チェッ クに ruby_class を使い、ruby_cbase に定義していることです。こ の二つの意味の違いはこうです。 ruby_class .... メソッドを定義する対象のクラス ruby_cbase .... 定数参照・代入の起点 この二つは通常は同じ値なのですが、module_eval 中では ruby_class = module_eval のレシーバ ruby_cbase = 変わらず になります。それでこの二つが違う値になっていると変な動作になる わけです。だからたぶん少なくとも ruby_class とruby_cbase、使う のはどちらかにすべきなんだろうと思います。 あとはネスト先を ruby_class と ruby_cbase のどちらに統一 すべきか、という問題です。具体的に言うと、 module M end class C M.module_eval { class SomeClass end } end としたときに、SomeClass は C と M のどちらにネストして定義され るべきなのか、ということです。M に定義されるなら ruby_class、 C に定義されるなら ruby_cbase を使うことになります。 ちなみに、1.6 までは M に定義されるようになっていました。 だからこんなこともできました。 # net/protocol.rb より def net_private( &block ) ::Net::NetPrivate.module_eval(&block) end net_private { class SMTPCommand # Net::NetPrivate::SMTPCommand end } しかし 1.7 になってからは「定数の挙動は静的であるべき」という 方針でこういうことはできないようにする方向に動いています。その 流れから言うと全部 ruby_cbase にするべきなんでしょう。 しかしクラス定義だけはちょっと違うのだ、というのもそれはそれで 納得できるものがあります。特にこの場合は module_eval したうえ でのクラス定義文ですから、やや特殊な状況です。例えばなんらかの コードの中で動的にクラスを定義しようとしたときに、ネストする先 が動的に変更できないと M.const_set :ClassName, Class.new {.....} などとするしか方法がなくなります。module_evalでネスト先を変え られれば M.module_eval { class ClassName .... end } で書けます。 個人的には、定数とクラス名の挙動は同じであるほうがいいと思うの で、「定数に揃える」に一票入れます。 ------------------------------------------------------------------- 青木峰郎