A Subtle Change to Mixin Semantics in Ruby 1.9

Posted by Rick DeNatale Mon, 09 Oct 2006 20:07:00 GMT

I’ve been working on a little tool to peek behind the curtain and see a bit of what’s going on behind the scenes in the standard Ruby implementation (i.e ‘ruby’ as opposed to ‘Ruby’)

While doing this I was looking at the code which ruby runs when you include a module in a class or another module. I noticed that ruby 1.8 was going to some pains not to move the proxy for an included module in the inheritance chain.

To verify what my eyes seemed to be telling me, I wrote a silly little test program which created a module with one method, and several classes.

This test verified my reading of the 1.8 code. I then tried the same test using the latest ruby 1.9 and found that module mixin semantics have changed.

Read on

Here’s my test code:
module M1
  def foo
    "foo in M1"
  end
end

module M2
  include M1
end

class C1
  def foo
    "foo in C1"
  end
end

class C2 < C1
  include M1
end

class C3 < C2
  def foo
    "foo in C3"
  end
end

class C4 < C3
  include M1
end

class C5 < C3
  include M2
end

puts "C1 #{C1.new.foo}"
puts "C2 #{C2.new.foo}"
puts "C3 #{C3.new.foo}"
puts "C4 #{C4.new.foo}"
puts "C5 #{C5.new.foo}"

Module mix-in semantics in ruby 1.8

And here’s what we get when we run this using ruby 1.8, with commentary interspersed:
rick@frodo:/public/rubyscripts$ ruby include_test.rb
C1 foo in C1
Note that class C1 defines a method :foo. There should be no surprise that invoking :foo on an instance of C1 gets that definition.
C2 foo in M1
C2 subclasses C1, and also includes module M1, which also defines a method :foo. We can see that M1’definition takes precedence. The search order is, first a singleton class if it exists, then the object’s class, then any modules included in that class in the reverse order of inclusion, then the superclass, etc.
C3 foo in C3
C3 subclasses C2 and overrides the foo method. Again no surprise.
C4 foo in C3
Here’s the case which illustrates what I found in the 1.8 code. C4, a subclass of C3 again includes M1, but instances of C4 resolve foo to the method in C3. This seems to be odd, since by including M1 in C4, I would expect M1’s methods to take precedence over those of the superclass.
C5 foo in C3
This final case is a class, C5 which like C4 subclasses C3, and includes a second module M2, which in turn includes M1. Once again foo resolves to the method defined in C3.

I find the 1.8 implementation counter-intuitive. As I said it goes to lengths to find an existing inclusion of a module, and re-use it, even if that inclusion was in a superclass.

Change in ruby 1.9

I guess that the core-team agreed with that assessment, or at least the current 1.9 implementation seems to do insertion in a less surprising manner. Here’s the result of running the same code with ruby 1.9:
$ ruby1.9 include_test.rb
C1 foo in C1
C2 foo in M1
C3 foo in C3
C4 foo in M1
C5 foo in M1
For easier comparison here’s the output from ruby 1.8 without commentary.
$ ruby include_test.rb
C1 foo in C1
C2 foo in M1
C3 foo in C3
C4 foo in C3
C5 foo in C3

I don’t know about you, but this seems more natural to me.

It’s a subtle change. I don’t know how much ruby code re-mixes in modules. I only wrote my test code to verify a quirk I saw in reading the ruby sources.

Since the ruby 1.8 implementation made doing so ineffective, I doubt that there is much code which does, but if there is such code, it might break under 1.9, but I think that the change is a good one.

Posted in  | Tags , , ,  | 1 comment | no trackbacks

Comments

  1. binary42 said about 2 hours later:
    While this hasn't been addressed like this before, it has been brought up multiple times but those seeking reason to the behavior 1.8 and bellow makes when including the same module multiple times. It has some nasty side effects for the uninitiated. 1.9 is fortunately just simplifying the notion of modules by removing this specific constraint. Somedays I wish I could move production code over to 1.9 today... some of these changes are so nice! :-) Thanks for the good write-up.

Trackbacks

Use the following link to trackback from your own site:
http://talklikeaduck.denhaven2.com/articles/trackback/48

Comments are disabled