Many people hear about Ruby mixins, but don't actually know what they are since they don't program in Ruby. Plus, I see a number of questions online about how to write a mixin in Ruby. So here's a quick example for Perl programmers to see how it's done (note: I'm hardly a Ruby guru, so be forewarned).
To use mixins in Ruby, first create modules with the behavior you want to share. A module can be thought of as an incomplete class which you cannot instantiate.
module Bomb def explode puts "Bomb explode" end def fuse puts "Bomb fuse" end end module Spouse def explode puts "Spouse explode" end def fuse puts "Spouse fuse" end end
Then in your class, simply include the mixins:
class PracticalJoke include Spouse include Bomb end
So what does that do?
joke = PracticalJoke.new() joke.explode joke.fuse
This nicely allows classes to simply be responsible for generating instances and shared behavior is handled by the mixins. Unfortunately, that prints:
Bomb explode Bomb fuse
In other words, we've exchanged multiple inheritance's "first wins" behavior with mixin's "last wins" behavior. Trying to get one method from each appears to need delegation.
Also, I've noticed that it appears that you're not flattening these methods into your class, as you would with roles, but are instead inheriting from the mixins:
irb(main):026:0> PracticalJoke.ancestors => [PracticalJoke, Bomb, Spouse, Object, Kernel]
This means that your mixin methods aren't really getting "mixed in" to your class. I'm not sure of the downside of this (aside from having a longer inheritance chain), but the upside is that you can at least override methods cleanly:
class PracticalJoke include Spouse include Bomb def explode puts "PracticalJoke explode" end end joke = PracticalJoke.new() joke.explode joke.fuse
And joke.explode will print PracticalJoke.explode. I think this means that if you want a warning at the PracticalJoke level about an accidental override, you're out of luck.
Further comments from Ruby experts welcome.
Re:Why?
Ovid on 2009-07-16T15:49:55
It's easy for this to happen, particularly in large systems. Here's what I'm working on right now:
$ find lib/ -name '*.pm'|grep -v 'Role/'|wc -l
600
$ find lib/ -name '*.pm'|grep 'Role/'|wc -l
42And we're still adding roles.
When you have a large system with multiple roles, it's likely that you'll eventually encounter situations where roles provide similarly named methods. This should generally only happen if these methods are semantically equivalent, but provide different implementations. As just one example from our code, we have "publication events". Anything which does('Role::PublicationEvent') event gets the entity_identifier() and memberships() methods, but anything which also does('Role::Identifier') must exclude the latter's version of entity_identifier() and memberships() because the behavior is different, even though they're semantically equivalent (actually, memberships is provided via a different role, but this is getting long enough as is).
Since these methods are semantically equivalent, disambiguating these methods via different names would be a bad idea because if we eventually find that we can drop support for one of those conflicting methods (will will for entity_identifier for publication events) and fall back on the original, we don't want to go through all of our code and remove the other method name. That being said, if you have identically named methods which do semantically different things, then you should consider renaming your methods.
As our system grows, we're finding a few more cases like this, but it's vitally important that we have the tools necessary to cleanly resolve this. Mixins don't provide us with that facility.
Re:Why?
joeri on 2009-07-16T16:03:11
Using a thing like the Ruby mixins for such cases is over the top.
The programming God tells me one should use simple delegation for such cases like they do in Cocoa (and it looks simple and beautiful).
Re:Why?
Ovid on 2009-07-16T16:12:28
Delegation is great, but when the thing delegating and the thing being delegated to need to communicate with one another, you start winding up with problems. We already have components (roles) that we can assemble and need to work well together and separating them out via delegation means a separate class heirarchy just have to have something to delegate to. Why would I want to write the scaffolding for delegation? Honestly, roles are really, really simple and make this trivial
:) Plus, delegation is for a has-a relation and that's not what our code is expressing via roles.
According to the Pickaxe book (figure 18.4) (page 247 of the first edition). Mixins modules are actually proxied. You can see in the diagram that it inserts a Proxy class between Guitar class and the Object class. The Proxy class does not have any methods itself, but instead it has a pointer to the method table of the module it is proxying. This gets more and more ugly and complicated the more modules you mixin, however it does explain the odd "last wins" behavior.
Of course, I am not sure if this is still how it is done, after all the first edition of the Pickaxe book is almost 8 years old now.
- Stevan
http://www.chadfowler.com/2009/7/8/how-ruby-mixins-work-with-inheritance