This is the paper to accompany my slides explaining what's wrong with inheritance and how traits (roles) help to solve these issues: http://www.slideshare.net/Ovid/inheritance-versus-roles
1. Eliminating Inheritance Via Smalltalk-Style Traits
Copyright 2009 by Curtis “Ovid” Poe under the GNU Free Documentation License1
A Brief History of Pain
Object-Oriented Programming (OOP) has been around for a long time. In fact, most of the
core features we associate with OOP -- inheritance, polymorphism, classes and so on --
were introduced in 1967 with the language SIMULA 67 2. In the case of inheritance, one
might believe that a concept hanging around for over four decades would have most of the
kinks ironed out but one might also believe in the tooth fairy.
Now if you ask people “what is essential to OOP”, youʼll get plenty of arguments over the
matter, but “inheritance” is one area that many, many people disagree about violently. In
fact, some languages such as Javascript, REBOL, Lua and Self avoid inheritance by
reusing code via cloning existing objects and adding the behavior they need -- though if
you squint, it often looks like single inheritance. But for languages that do implement
inheritance, things should be pretty clear-cut, right?
In inheritance, we can think of classes as types and subclasses as subtypes. So Dogs
and Cats might be subclasses of Mammal and if you adhere to the Liskov Substitution
Principle3 (LSP), you could drop any instance of a Dog or Cat into a place where we
expect a Mammal and the code should run just fine. Of course, the devil is in the details
and depending upon the languageʼs type system, violating the principle might be a
compile-time error, a runtime error, or no error at all. Various mechanisms such as
programming by contract, used by Eiffel4, or the unusual inheritance mechanism provided
by Beta5 can be used to enforce LSP. Other languages, such as Ruby, use “duck typing”
where you just sort of hope that the object in question has the methods you want (to be
fair, this seems to work remarkably well for Rubyists). Still others argue that LSP isnʼt all
itʼs cracked up to be and debate whether a 3D point should inherit from 2D point or vice
versa and argue that somehow Liskov js flawed.
Then there are discussions about using aspect-oriented programming to enforce strict
equivalence, C++ templates versus Java generics, interfaces versus mixins, separation of
implementation and interface and so on. Whatʼs important here is that these arenʼt
debates about a piece of code you implemented last week. These are serious debates
about the implementation of the languages themselves. While most people are generally
on board about things such as polymorphism and encapsulation, arguments about
appropriate use of inheritance have been going on for over four decades.
1 http://en.wikipedia.org/wiki/Text_of_the_GNU_Free_Documentation_License
2 http://heim.ifi.uio.no/~kristen/FORSKNINGSDOK_MAPPE/F_OO_start.html
3 http://www.objectmentor.com/resources/articles/lsp.pdf
4 http://www.eiffel.com/
5 http://www.daimi.au.dk/~beta/
2. If you want to dig further into this, youʼll discover that the inheritance mess is so serious
that many top-notch OOP developers recommend that you avoid inheritance as much as
possible and use delegation, mixins, or other tricks to implement code reuse.
This, my friends, is what we call a “code smell”. A code smell doesnʼt mean that thereʼs a
problem with code, but it does mean that it bears further investigation. When you have a
programming practice thatʼs been around for over four decades and people are still
arguing about the fundamentals of it, further investigation is definitely warranted. So letʼs
investigate.
A Deeper Look At Pain
First off, Iʼm going to go out on a limb and suppose that most readers of this are not
programming language designers. Language designers have a much harder problem than
language users. They shouldnʼt just hack something together and hope it works, they
have to research it. They have to think about it very carefully. They have to consider how
their language is likely to be actually used and they have to implement it to support that
use. Some languages, like Eiffel, are very carefully designed and others grow
organically 6, but most OOP languages that you know probably have had a bit of thought
applied to how they implement OOP. This raises a couple of interesting point.
First, if the language is even remotely popular, it probably means that the author(s) of the
language probably has a lot more on the ball than most of us. Second, they had a lot
more time to develop their OOP implementation. So better programmers with more time
on their hands will probably have an implementation of code will likely be better than our
implementation of code. Now if youʼre like me, youʼre probably an average programmer
working with a deadline and you need to translate your (possibly ill-conceived) specs into
the language designerʼs carefully thought-out language and thereʼs a good chance that
what you create wonʼt be perfect the first time around. Real world translation: most “OO”
code bases tend to be a mess. Iʼve worked with plenty of them in a variety of languages
and theyʼre often horrific and tough to maintain, but the key thing is that they work. They
work despite violating the Liskov Substitution Principle, despite ignoring strict equivalence,
despite not understanding cohesion. They have all sorts of nasty hacks to work around
the developerʼs lack of knowledge and this makes them a nightmare to maintain and
extend, but they work.7 This is because programmers generally arenʼt focused on theory.
Theyʼre focused on behavior. “I need this method to read a config file”, so they write the
code to do that. Sure, maybe they should be abstracting this into a ConfigManager
class, but when they think about their deadline, timeʼs a-wastinʼ.
Truth be told, I donʼt think this is a problem with developers. Yes, itʼs a problem that
developers need better training and experience, but at the end of the day, while some
developers are reading a paper on the original intention of the database relational models
in databases (sadly, far too few), other developers are out at the pub, sharing a pint with
friends, going on dates with their partners and spending time with their children. This isnʼt
a bad thing. Itʼs OK to have a life outside of programming. Whatʼs a bad thing is that to be
a great programmer, you usually find yourself forgoing a lot of that life. What we need are
programming tools that fit what developers really do rather than try to suddenly expect that
6 The popularity of PHP says a lot in favor of practice over theory.
7 Though I confess to still having nightmares about that C# developer who wrote a “StartHTML” class.
3. developers are going to “step up their game.” Inheritance is not one of those programming
tools.
To investigate some of this, Iʼm going to focus on Perl. Though an oft-maligned language,
its flexibility and power have a lot to recommend it. And, to be honest, though Iʼve
programmed in a variety of different languages, Perl is the one I now know best and that
means I can more easily focus on concepts and worry less about rabid readers attacking
me for missing a semi-colon.
First, letʼs take a look at a real-world case of inheritance:
Figure 1: The B Inheritance Hierarchy8
Without going into too much detail, thatʼs an inheritance hierarchy which simulates how
Perl variables work internally. For the end user this tends to be transparent, despite the
apparent complexity and that's part of what classes should do for you. Still, very few Perl
programmers ever need to touch this and fewer still know what it means. However, letʼs
take a closer look at one section of it.
8 Full size image: http://www.flickr.com/photos/publius_ovidius/3646752998/sizes/o/
4. Figure 2: A Closer Look At A Section of the B Inheritance Hierarchy
Hmm, looks like we might have some multiple inheritance here, so letʼs focus on just that:
Figure 3: Examining PVIV Inheritance
5. With a large amount of hand-waving, “SV” means “variable”. “PV” is a string value and
“IV” is an integer value9. A “PVIV” is a variable with both string and integer
representations. To those working with languages with strict type systems, this seems
absurd. However, in Perl, this means that you can do something like this:
my $number = 3;
$number += 2;
print "I have $number apples";
Example 1: Printing A String In Perl
And that final line prints “I have 5 apples”.
In Java, those lines might look like this:
int number = 3;
number += 2;
System.out.println("I have " + number + " apples");
Example 2: Printing A String In Java
Java doesnʼt allow operator overloading, but theyʼve made an exception for the String
class. Perl allows operator overloading but often doesnʼt need it because variables have
“slots” (again, lots of hand-waving) with string, integer and numeric (double) values and
each is used as appropriate given the context. Thatʼs why thereʼs this strange “PVIV”
class. Most of the time this “just works”, but what most people donʼt know is that while the
string, integer and numeric slots usually have complementary values (“5”, 5, 5.0), you can
assign different values to those slots if you really need to (“five”, 5, 5.0)10 and ensure that
the final line in the Perl code prints “I have five apples” even if the integer value is 5.
Looking further, both the B::PV and B::IV classes have an as_string method and if
you have a B::PVIV instantiation of a variable, printing its string value is trivial:
print $variable->as_string;
Example 3: Printing A String In Perl
That prints the B::PV::as_string value because B::PVIV inherits from B::PV first.
Ignoring for the moment the fact that this contrived example is a usually a stupid thing to
do, what happens if you want the as_string value of the integer 5 and not the
as_string value of the string “five”? Because of how multiple inheritance works and
because of how these classes are designed internally, this becomes rather painful to get.
print $variable->B::IV::as_string;
Example 4: Printing An Integer Value in Perl
9"SV": Scalar value. "PV": Pointer value ("S" was taken and a string in C is merely a 'p'ointer to an array of
chars. "IV": Integer value.
10This technique is known as a “Big Bucket of Stupid”, but some people prefer to call it a dualvar. See
"dualvar NUM, STRING" in http://search.cpan.org/~gbarr/Scalar-List-Utils-1.21/lib/Scalar/Util.pm Despite
my mockery, it does sometimes prove very useful.
6. In short, weʼre forced to encode knowledge of our class structure into the method call and
we have inheritance leading to a tremendous violation of encapsulation. Polymorphism,
C3 linearization11 and other OOP techniques quietly fail here. If you have an instance of a
class, you really shouldnʼt have to know about the structure of the class hierarchy to use it,
but there you go.
Real-World Pain At the BBC
At this point youʼre probably thinking that Iʼm smoking crack. This is a contrived example
and is so incredibly obscure -- though itʼs the sort of real-world uses the the B:: classes
were designed to support -- that it doesnʼt seem relevant to what you do. So hereʼs a real-
world (simplified) example of a problem we faced building a metadata system for the
BBC.12
11 http://en.wikipedia.org/wiki/C3_linearization, a.k.a. “Mopping the Titanic”
12 http://www.bbc.co.uk/blogs/bbcinternet/2009/02/what_is_pips.html
7. Figure 4: A BBC “Program” (which my British colleagues insist upon spelling as “programme”)
This was part of our inheritance hierarchy for a program that you might watch on the BBC.
Now OOP purists might shudder at this hierarchy, but look at this from the point of view of
your average programmer.
My::ResultSource is a single instance of an object we pull from our database object-
relational mapper DBIx::Class.13 Most objects need to be audited (“who changed
what?”), so an Audited object ISA My::ResultSource. All objects which need to be
tagged (for example, tags you see on blog posts) are audited, so Tagged ISA Audited.
Thus, any Program which needs to be tagged ISA Tagged. No, Iʼm not arguing that this
is an appropriate use of OOP. Iʼm arguing that for the average developer, it doesnʼt
necessarily seem that ridiculous.
If someone alters an instance of a Programme (switching to the British spelling to
distinguish from a computer “program”), the program automatically Does The Right Thing
and works its auditing magic. Part of the reason it does this is because we, at present
count, have over 30,000 tests for our system and partly because this inheritance hierachy,
while violating a lot of rules, just works. My colleagues are a bunch of talented developers
who Iʼd be happy to hire onto any company I work with (really, all of them -- Iʼm very lucky
about this), but they've created an inheritance hierarchy several levels deep and even
though itʼs only single inheritance in this example, we have started to run into problems
with the very flexible nature of Perlʼs OOP behavior.
For example, letʼs say that your Program class needs a from() method indicating where
we got the the programme from. Because we have a deep inheritance hierarchy, we have
to search through a number of classes to know if weʼre accidentally overriding something.
As it turns out, DBIx::Class::ResultSource implements a completely different
from() method and overriding that will break out code. Only because we have a good
test suite are we protected from that. So itʼs fair to say that deep single inheritance tree
can increase the cognitive load a programmer has to manage and thatʼs something we like
to avoid.
As a quick aside: Java programmers might very well stop here, point to their IDEs and
laugh, explaining that their IDE can automatically point out when they're overriding
something. With statically typed languages, such IDE support is common. However,
dynamically typed languages generally donʼt have strong static analysis tools available
and thus frequently canʼt take advantage of this.14 Tools for managing this sort of
complexity -- and large-scale software is all about complexity management -- should be
built into the language when possible and not rely on external software products.15
But moving along, I should point out that the BBC systems store more than just
programmes. For example, we also store what we call “reference data”. This is data
13 http://search.cpan.org/dist/DBIx-Class/
14Dynamic languages have other benefits and this limitation is hardly an indictment, so donʼt choose
languages based on a single pet peeve. Plus, the dynamic SmallTalk languageʼs class browser provides a
delightful counter-example.
15Anyone forced to use "vi" (not even "vim") while trying to create an emergency patch of broken code over
a slow telnet connection at 2:30 in the morning is going to get very irritated if your codebase is so complex
that you need a large IDE to comprehend it.
8. which, unlike programmes, changes very infrequently, if at all. In your online store
database, you might have a table listing US states. Those donʼt change very frequently
and we might have a different base class named My::ResultSource::Static which
might extend some of the behavior of My::ResultSource. Since the BBC has
programmes from all over the world, we might have static data for countries.
Figure 5: Our Inheritance Hierarchy Grows
Since the static (reference) data doesnʼt change much, we donʼt need to audit it and our
inheritance hierarchy, while becoming a bit more complex, is still single inheritance. But
then the unthinkable happens: the Berlin Wall falls, the Soviet Union collapses, new
Eastern European countries are popping up like popcorn. All of a sudden, weʼre changing
our “static” country data quite a bit. Though our Country class still might benefit from a
“static” base class, we now need to audit it because our editors are constantly updating it.
Then our real problem starts.
Country canʼt simply inherit from Audited because it will no longer inherit from
My::ResultSource::Static. However, My::ResultSource::Static canʼt inherit
from Audited because our other static data might not need auditing. The solution seems
to be falling back on multiple inheritance.16
16One reviewer claimed that he had trouble following this example and asked if I could provide a clearer
one. I was initially inclined to agree until I realize that his complaint supported the argument presented in this
paper. Specifically, this example is a simplification of a real-world problem and if the inheritance is
confusing, that's because this is what happens with inheritance.
9. Figure 6: Solving A Compositional Problem With Multiple Inheritance
The dangers of multiple inheritance are so well-known that I will not belabor them here, but
suffice it to say that since so many OOP languages disallow multiple inheritance,17 itʼs fair
to say that there might be a reason for avoiding it. And though the simple example in
Figure 6 might look manageable, the reality is, systems grow.
Figure 7: The “Moose” Inheritance Hierarchy (still relatively small)18
As mentioned back at Figure 4, those with a strong OOP background might shudder at the
Country/Program hierarchy and, in fact, theyʼd be right. The problem kicking its head up
is “separation of concerns”.19 If youʼre trying to avoid multiple inheritance, you might very
well call your team lead over, show her the problem, and have her give a long lecture
about how a Program ISA Audited is a terrible way of modeling the system and auditing
functions belong in a separate class hierarchy which a Program delegates to and while
youʼre at it, you shouldnʼt be inheriting from Tagged either.
17 C#, Objective-C, Object Pascal, Java, PHP, BETA and Smalltalk, to name a few.
18 Full size image: http://www.flickr.com/photos/publius_ovidius/3549146507/sizes/o/
19 http://en.wikipedia.org/wiki/Separation_of_concerns
10. Shamefacedly, you admit that sheʼs right and as she walks away, you quietly moan about
all of the extra work you have to do when all you wanted to do was having “Country” write
a single line to a log file.
And thereʼs the disconnect. Programmers just want to Get Stuff Done and quite often we
find that trying to model a large system “properly” (whatever that means) is hard. Most
programmers arenʼt OOP experts and even those that are will generally tell you that the
first iterations of OOP systems tend to have plenty of flaws in them. CRC cards,20 UML
diagrams,21 and similar techniques, while laudable, donʼt seem to be employed very
often.22
Taking Pain Killers
So whatʼs really the problem going on here? A large part of the problem lies in how we
use classes. Specifically, we use them for two different things.23
First, as agents of responsibility, a class needs to perform everything the class is
responsible for. While this sounds obvious, it does mean that we have an upward
pressure on class size. As systems grow and we need new features, we add them to our
classes.
Second, classes are used for code reuse. This is done primarily (but not exclusively) via
inheritance. This causes an interesting issue. In Perl, when you write a library itʼs
generally recommended that you not pollute the namespace of code which uses your
library, but instead allow the consumer to choose which of code they want exported into
their namespace. For example:
use List::Util qw(reduce);
my $product = reduce { $a * $b } 1 .. 10
Example 5: Mitigating Namespace Pollution in Perl
Though List::Util offers several list utilities, you only get the ones you really need.
Thatʼs great because then you have control over what youʼre getting and frankly, many
times youʼre using a library and you only want one function out of it, not all. The same
goes for classes, but if you inherit from them, you get all of the methods those classes
provide,24 so what you really want are smaller classes so that you donʼt pull in irrelevant
behavior.
So classes are used in two different ways which tends to give them competing
requirements of needing to be both both smaller and larger at the same time. This is
20 http://en.wikipedia.org/wiki/Class-Responsibility-Collaboration_card
21 http://en.wikipedia.org/wiki/UML_Diagram
22http://www.google.com/search?hl=en&q=uml+sucks and numerous books touching on these topics.
Additionally, your author has worked for a number of companies which list UML as a job requirement, but
never actually use UML.
23 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
24 Thatʼs not entirely fair. Some languages limit this, but you still may very well get methods you donʼt need.
11. actually the root of the problem with classes and the solution here is to decouple these two
uses. Enter “traits”.
Traits, in this sense, refers to SmallTalk-style traits. These were first described in “Traits:
Composable Units of Behaviour”25 in 2003. Though a relatively new concept in computer
science terms, traits present a coherent alternative to code reuse via inheritance. Far from
being a wild experiment, trait research was carried out at two separate universities under
grants from the National Science Foundation and the Swiss National Foundation26 and for
the masochistic, you can read a “A Typed Calculus of Traits”27
The basic idea of traits is simple. You identify a behavior you need to share across
classes and you push it into a trait instead of a parent class. The trait provides the
methods28 and lists other methods it requires.
For example, letʼs say that all of your objects can be serialized as YAML, but you need to
serialize them as XML. The trait might look like the following pseudo-code:
trait XMLSerialization {
import XML::Converter;
requires 'Str as_yaml(void)';
Str as_xml(void) {
return XML::Converter.from_yaml(this.as_yaml());
}
}
Example 6: Pseudo-code For A Trait
And your class might look like this:
class Customer does XMLSerialization {
Str as_yaml(void) {
...
}
}
Example 7: Pseudo-code For Consuming a Trait
Later, someone else might write this:
if ClassOf(customer).does(‘XMLSerialization’) {
print customer.as_xml();
}
Example 8: Pseudo-code Of Trait Introspection
25 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
26 http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
27 http://people.cs.uchicago.edu/~jhr/papers/2004/fool-traits.pdf
28 Strictly speaking, these are argued to be pure methods, but in practice, thatʼs not always what happens.
12. So now we have a way of organizing shared behavior without worrying about inheritance,
but honestly, if this was all there was to traits, it wouldnʼt be that compelling. Fortunately,
this is not all there is to traits. Now weʼre going to switch to examples in Perl 5 because
aside from the fact that this is the language Iʼm now most comfortable working in, itʼs also
probably the language which has the most widespread adoption of traits 29 and thereby has
a solid implementation. In Perl, however, traits are called “roles” (due to the term “trait”
already in use with Perl 6), so weʼll start using that term now. Further, weʼre going to use
Moose30, a complete OO system for Perl with a built-in metaprotocol.31 It makes our OOP
code more legible and has Moose::Role included, allowing you to use roles.32
First, imagine that youʼre writing a text adventure and there's a room with a practical joke
in it which must serve as some sort of clue to the player. So you start writing a
PracticalJoke class and it needs a non-lethal explosion and a fuse. Fortunately, you
already have fuse() and explode() methods handy in Bomb and Spouse classes. So
hereʼs what you write:
# In Perl 5, packages and classes are the same
package PracticalJoke;
use Moose;
extends qw(Bomb Spouse); # i.e., "inherits from"
Example 9: Multiple Inheritance In Perl
Right off the bat, we have a problem. Both Bomb and Spouse provide fuse() and
explode() methods. Here are their properties:
Method Description
Bomb::fuse() Timed
Spouse::fuse() Non-deterministic
Bomb::explode() Lethal
Spouse::explode() Wish it was lethal
Table 1: Multiple Inheritance For The FAIL
As you can see, this wonʼt work. We need the timing from the Bomb::fuse() method,
but the non-lethal Spouse::explode() behavior. Obviously, multiple inheritance means
29 http://scg.unibe.ch/research/traits (see the “Perl” section)
30 http://search.cpan.org/dist/Moose/
31 http://en.wikipedia.org/wiki/Meta-object_protocol
32Please note that this is not a tutorial on roles. Theyʼre trivial to learn and the details will vary from
language to language (if your language supports them). Instead, I focus on the concepts at hand rather than
getting sidetracked with detail.
13. that when we call these methods, weʼll get it from one base class or another, whichever is
first in the inheritance hierarchy.33
A word about mixins is in order. Some people like to share behavior via mixins, but theyʼd
have the “last wins” problem and would be of no help here. Consider the following Ruby
code:
33Actually, the language Eiffel would detect this issue and require you to be specific about which methods
you wanted. Joe Bob says “check it out”.
14. 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
class PracticalJoke
include Spouse
include Bomb
end
joke = PracticalJoke.new()
joke.explode
joke.fuse
Example 10: Mixin Conflicts In Ruby
That will print out "Bomb explode" and "Bomb fuse". In other words, the Bomb's methods
overwrite the Spouse methods. The methods of the last module mixed into your class will
silently overwrite the previous module's methods. To try and reuse one method from each
means that one or both of those need to be handled differently. Additionally, mixins as
currently implemented provide no introspective capability letting outside code know that
your class can "do" a given mixin. With roles, it's a matter of asking the object's meta
class if it performs a given role:
if ( $object->meta->does_role($some_role) ) { ... }
Example 11: Role Introspection In Perl
We could use delegation to solve the "which method can we use" problem caused by
multiple inheritance or mixins, but now you have a new problem that you have to set up
whatever scaffolding your language requires for delegation. Plus, delegation often means
that you send a message to the receiving object and if it needs more information, it canʼt
always communicate with the original invocant.
With multiple inheritance, you can often ask if a class ISA different class which supports
the behavior you need but you get the troubles with inheritance. With both delegation and
mixins, you avoid some (not all) of the issues with inheritance by you often can't
programmatically determine whether or not a given instance supports the behavior you
need. When using roles, you can simply ask a class or instance if it performs that role and
15. take action accordingly. Delegation obscures this. Roles, however, make this issue trivial
as Example 11 shows.
Here's how you might write the PracticalJoke class in Perl, using roles:
{
package Bomb;
use Moose::Role;
sub fuse { print "Bomb exploden" }
sub explode { print "Bomb fusen" }
}
{
package Spouse;
use Moose::Role;
sub fuse { print "Spouse exploden" }
sub explode { print "Spouse fusen" }
}
{
package PracticalJoke;
use Moose;
with qw(Bomb Spouse);
}
my $joke = PracticalJoke->new();
$joke->explode();
$joke->fuse();
Example 12: The PracticalJoke Class In Perl, Using Roles
Except that code won't compile.34 It fails almost immediately with a stacktrace and the
error message "Due to a method name conflict in roles 'Bomb' and
'Spouse', the method 'fuse' must be implemented or excluded by
'PracticalJoke'".35
package PracticalJoke;
use Moose;
with 'Bomb' => { excludes => 'explode' },
'Spouse' => { excludes => 'fuse' };
Example 13: Excluding Conflicting Role Methods In Perl
And the order of consuming those roles is irrelevant, unlike multiple inheritance or
mixins.36
34 I'm lying to you. It does compile, but fails at "composition" time. With typical usage of Moose, you
frequently won't notice the difference.
35 This is for Moose 0.81 and it should report both the fuse() and explode() methods as being in
conflict. Currently it reports one and when you resolve the conflict, it reports the other. A bug report has been
filed.
36
That's also not entirely true. I'm apparently a pathological liar. There are things you can do which would
make the order of role consumption important, but you generally have to try to achieve this.
16. All of a sudden, things start to look a bit interesting for traits. The order in which you
compose roles is irrelevant and you have full control over those methods. In fact, you
have more control than what you see here. Still want to sometimes have the
Spouse::fuse() method available?
package PracticalJoke;
use Moose;
with 'Bomb' => { excludes => 'explode' },
'Spouse' => { excludes => 'fuse',
alias => { fuse => 'random_fuse' } };
Example 14: Aliasing Conflicting Methods, Rather Than Simply Excluding Them
And in your actual code:
$joke->fuse(14); # timed fuse
# or
$joke->random_fuse(); # who knows?
Example 15: Calling Both The Needed fuse() method and its aliased version.
Whatʼs even more interesting is that if you have conflicting methods, your code wonʼt even
compile 37, but will instead fail with a useful error message and stack trace.
So not only is it easy to use a role, it's easy to write one, too.
And what about the poor programmer who has the multiple inheritance mess? His
inheritance hierarchy now looks like this:
Figure 8: No More Multiple Inheritance!
37 Actually, it fails at composition time, but we wonʼt go there.
17. And now he can fall safely back to his old “cut-n-paste” coding and update “Country” by
merely pasting in the “DoesAuditing” role:
package Country;
use Moose;
extends "My::ResultSource";
with qw(DoesStatic DoesAuditing);
Example 16: Fixing the Country Multiple Inheritance Problem
If the Country class doesnʼt provide all of the methods that DoesAuditing requires, or if
there are method conflicts, he gets a compile-time failure. In practice, weʼve found at the
BBC that many times, the code simply “just works” by applying a new role. In fact, these
techniques are proving powerful enough that other teams in the BBC are starting to switch
to roles with similarly pleasing results.
A Real World Example
Now those are rather trivial examples, but hereʼs a real example from part of the metadata
project I work on. We have one subsystem which returns “resultsets”. These are sets of
objects from the database. The old class hierarchy looked like this (again, this is only a
small part of the system):
Figure 9: Old ResultSet Hierarchy 38
If you look closely, youʼll notice that we donʼt have multiple inheritance here -- though we
did in other parts of our system -- but we do have several levels of inheritance, making it
difficult at times to know where behavior was implemented. After converting this system to
roles, hereʼs our new inheritance hierarchy:
Figure 10: New ResultSet Hierarchy39
I realize that this is tough to see, but itʼs completely flat, with only a single abstract base
class. If you open up a particular class, you might see something like this:40
38 Full size image: http://www.flickr.com/photos/publius_ovidius/3426561336/sizes/o/
39 Full size image: http://www.flickr.com/photos/publius_ovidius/3426561340/sizes/o/
40Actually, that code is a lie. I've cleaned up some of the names to make them a tad more understandable
to people unfamiliar with our system. For something closer to the truth, here's an old diagram of the
ResultSet hierarchy with roles listed: http://www.flickr.com/photos/publius_ovidius/3425903699/sizes/o/
18. package BBC::Programme::Episode;
use Moose;
extends 'BBC::ResultSet';
with qw(
DoesSearch::Broadcasts
DoesSearch::Tags
DoesSearch::Titles
DoesSearch::Promotions
DoesIdentifier::Universal
);
Example 17: What A Full List Of Consumed Roles Might Look Like
In the process of refactoring, we found that many of those roles were originally behaviors
shared across classes which should not implement them. We didnʼt see this at first
because we have a large system and these “hidden” behaviors were safely tucked away in
base classes. These were serious bugs waiting to happen, but by refactoring into
appropriately named roles, any programmer can open up a class and see at a glance
which behaviors it really implements and if the roles are well-named, errors become much
more apparent. Not only do we get composition safety, we gain comprehension.
Conclusion
The power of roles -- “traits” if you prefer -- is that they allow classes to resume a more
natural role (er, sorry about that) as agents of responsibility. Shared behavior is handled
via roles. By making this separation, the BBC has found that we have better compile time
safety, fewer “hidden” methods and classes whose behaviors are far more
comprehensible, even to newer programmers.
Of course, roles can only mitigate risk, not eliminate it. In Perl 5, for example, you donʼt
have method signatures, so there can be some ambiguity when a role lists the methods it
requires, but in practice, weʼve not even found this to be a problem 41, despite it being a
glaring shortcoming of Perl 5.42 We are now developing code faster and with greater
understanding. Though the concept of roles is only a few years old, itʼs rapidly proving to
be an excellent technology.
41 To be fair, our comprehensive test suite may account for a lot of this.
42Perl 6 has proper method signatures and theyʼre far more robust than most languages and Moose
extensions are adding them to Perl 5.
19. Addendum: A Quick Note About Dynamic Languages
The debates about static versus dynamic languages rage even stronger than the debates
about inheritance. In my opinion, much of the debate is rather silly as many people don't
really understand what type systems are all about.43 When I find myself programming in a
static language, I often find myself missing many of the features which make dynamic
languages so flexible. However, when I'm programming in dynamic languages, I miss
features which make static languages a bit "safer" to use. One example:
print $customer->as_xml;
Example 18: Does The as_xml() method exist?
In many dynamic languages, methods like that can be generated at runtime and as a
result, you can't know at compile time if this is an error. Consider the Java equivalent:
System.out.println(customer.as_xml());
Example 19: Java Compile-Time Safety
With a static language, that code won't even compile. You don't worry as much about
frantic 3:00 AM phone calls due to that method not existing. Roles can mitigate this by
specifying methods they require.
package DoesSerialization::YAML;
use Moose::Role;
requires 'as_xml';
sub as_yaml {
my $self = shift;
my $xml = $self->as_xml();
# convert XML to YAML and return
}
Example 20: Using Roles to Bring Some Static Language Safety to Dynamic Languages
If the class which consumes the DoesSerialization::YAML role fails to implement
as_xml() either directly or via another role which provides this method, you get a
composition-time failure44 and your code will not run. Thus, roles can help bring some
static safety to dynamic languages.
43 See http://www.pphsg.org/cdsmith/types.html for a nice introduction.
44 As with so many things about technology, there are techniques to make this a runtime failure if you really
like being woken up at 3:00 AM.
20. Reviewers
Iʼm grateful for the following individuals who contributed thoughts and advice on this paper.
Errors, of course, are still mine. They are listed below in no particular order.
• Zbigniew Lukasiak
• Jerry Sievert
• Anton Berezin
• Tim Brown
• James Laver