社内で Rubyのしくみの読書会をやっていて、6章の「メソッド探索と定数探索」を読みながらコードを動かしている最中に気がついた。確か以下の記述の検証をしていたときだと思う。
モジュールをインクルードすると、 Rubyはクラスの スーパークラスのリンクトリストに、 そのモジュールのコピーを挿入する
PatShaughnessy. Rubyのしくみ Ruby Under a Microscope (p.150). Kindle 版.
Ruby 2.6.6
$ irb irb(main):002:0> module M irb(main):003:1> def hello; end irb(main):004:1> end => :hello irb(main):005:0> class A irb(main):006:1> include M irb(main):007:1> end => A irb(main):008:0> a = A.new => #<A:0x00007ffc5f950ef8> irb(main):009:0> module M2 irb(main):010:1> def foo; end irb(main):011:1> end => :foo irb(main):012:0> module M irb(main):013:1> include M2 irb(main):014:1> end => M irb(main):015:0> a.foo Traceback (most recent call last): 4: from /Users/ikaruga777/.asdf/installs/ruby/2.6.6/bin/irb:23:in `<main>' 3: from /Users/ikaruga777/.asdf/installs/ruby/2.6.6/bin/irb:23:in `load' 2: from /Users/ikaruga777/.asdf/installs/ruby/2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 1: from (irb):15 NoMethodError (undefined method `foo' for #<A:0x00007ffc5f950ef8>) Did you mean? for irb(main):016:0> a.class.ancestors => [A, M, Object, Kernel, BasicObject] irb(main):017:0>
Ruby 3.0.1
irb(main):003:1* module M irb(main):004:1* def hello; end irb(main):005:0> end => :hello irb(main):006:1* class A irb(main):007:1* include M irb(main):008:0> end => A irb(main):009:0> a = A.new => #<A:0x00007ffc8a17a460> irb(main):010:1* module M2 irb(main):011:1* def foo; end irb(main):012:0> end => :foo irb(main):013:1* module M irb(main):014:1* include M2 irb(main):015:0> end => M irb(main):016:0> a.foo => nil irb(main):017:0> a.class.ancestors => [A, M, M2, Object, PP::ObjectMixin, Kernel, BasicObject] irb(main):018:0>
2.x まではモジュールをインクルードしたクラスの定義後に、インクルードされたモジュールに対して更にモジュールをインクルードしても継承チェーンは反映されなかった。しかし、3.0.0 からは継承チェーンに後からインクルードしたモジュールが反映されるようになる。
ここらへんをみて納得した。