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 C1C2 foo in M1C3 foo in C3C4 foo in C3C5 foo in C3I 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$ ruby include_test.rb
C1 foo in C1
C2 foo in M1
C3 foo in C3
C4 foo in C3
C5 foo in C3I 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.










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.