Damian Conway's Object Oriented Perl improved my programming more than any other book, when I first returned to programming. By the time I joined the Perl Renaissance, I'd learned more about how Perl 5 worked and how to think about approaching problems from Damian's book and my own experiments based on his writings.
Perl 5's default object system is deliberately minimal. It's the combination of two existing ideas introduced in Perl 5 (references and packages) with a method dispatch system. It's very clever in its minimalism; it enforces very little and almost never precludes people from using it to provide more powerful -- or merely different -- object systems.
It's not easy to make a minimal system that flexible.
Unfortunately, there are a couple of warts. It's common knowledge that the default Perl 5 object system is a little bit too minimal. Flexibility is good, but more people benefit from good defaults they only have to change when they're doing something special.
Most of the other problems I have with the basic Perl 5 object system come directly from its influence: Python. I laugh (yes, a sardonic laugh) every time a Python advocate says that Perl 5's object system is a bolted on hack, because Python has many of the same design problems. Yet I digress.
One design decision which Perl 5 stole wholeheartedly from Python is the idea that methods are just subroutines invoked as methods. Python's has first-class functions and allows you to install them in namespaces with simple assignment:
class Foo:
def bar(self)
print "I'm in bar!"
baz = bar
def main():
foo = Foo()
foo.bar()
foo.baz()
main()
This is not the place to debate the relative merits of Python versus Perl 5
syntax for doing so, but Perl 5 allows something very similar. You can
import()
methods into classes. The
Why of Perl Roles explains why this is important.
For the most part it works. Unfortunately, when it doesn't work, it really doesn't work.
Super Fragile Explodealicious
Consider a silly example:
package Foo;
use Modern::Perl;
sub new { bless {}, shift }
sub foo { say shift . '->foo()' }
Now subclass it:
package Bar;
use base 'Foo';
You can successfully instantiate a Bar
object and call foo()
on it:
Bar->new()->foo();
What happens if Bar
needs to get a method from a role? Here's
a Baz
package which (manually) imports a foo()
method
into the calling class. This method emits an informative message, then
redispatches to the parent method:
package Baz;
use Modern::Perl;
sub import
{
my $caller = caller();
no strict 'refs';
*{ $caller . '::foo' } = \&foo;
}
sub foo
{
my $self = shift;
say "foo() in Baz role!";
$self->SUPER::foo( @_ );
}
Unfortunately, now you can't call the foo()
method on
Baz
objects anymore:
foo() in Baz role!
Can't locate object method "foo" via package "Baz" at ...
Insufficient Dynamicity
This error message makes little sense. The foo()
method
within the Baz
package generates this error.
The problem is how the SUPER::
method selector works in Perl
5.
When the Perl 5 compiler encounters a function, it stores compile-time information in the internal data structure which represents functions. This CV, or Code Value, contains a pointer to the package to which the function belongs.
At runtime, the SUPER::
method redispatch looks at the package
into which Perl 5 compiled the current method, then looks in its list
of parent classes to figure out which method to call next.
You can see the problem. This behavior is, I believe, largely an artifact of a particular implementation -- likely the intersection of several sensible design decisions which combined to produce an unfortunate corner case.
Unfortunately, this behavior is unlikely to change anytime soon in Perl 5 (no matter how broken a feature, you can't argue a non-existence proof for code no one can see). The correct behavior is to redispatch based on the current class of the invocant. This is what the SUPER module from the CPAN does instead.
Note that Moose solves this problem in a similar way.