Putting does() in Intermediate Perl
I'm thinking about what a second edition of Intermediate Perl would look like. Of course, I would update it for Perl 5.12 (having been targeted at 5.8). Part of that is the new Perl 5.10
does() feature that replaces most uses of
isa(). I want to come up with some good examples to show off
does() and roles for the next edition. It's not as easy as I thought it would be.
I won't go into the history of
does(): look for chromatic's various writings on that. In short,
does() asks something it if has a set of behaviors. It's almost that interface stuff that Java has.
There's a problem constructing working examples though. Who gets to define the names of the behaviors? There is still the false cognate problem that
does() was supposed to ameliorate. Two different interfaces give themselves the same name. It doesn't have to be for any good reason. Let's just leave it at "people suck at naming".
How does Java solve this? There's an authority mechanism to distinguish class names when it matters. Perl has a different authority mechanism, called PAUSE, but not really. You upload something and if you use that package name first, it effectively belongs to you, but only in terms of the filename. A programmer can easily replace a class, add or remove methods, or plain just use the same name for a completely different purpose. Is that CGI class for web stuff or animation, even if they both have the
save method I'm interested in?
So, as I'm sitting here trying to come up with a good example to motivate
does(), I'm back to
isa(), the default behavior of
does(), because the only authority we have to lock a set of behavior to a string is PAUSE, and even that isn't very authoritative.
Now, this doesn't matter if we are making our own system of classes and objects because we make ourselves the authority and we can give the roles any names that we like. We get to control all the dimensions for our own application.
Let's suppose, however, that as an attentive CPAN author I'm thinking how I can tell other code what roles my modules handle. This isn't application development. I make this little piece over here, someone makes some other little piece over there, and someone else puts our two little pieces together. Maybe I have some code that delegates some Log4perl method names to an internal Log4perl object. That would be quite a handy bit of knowledge at the higher level when it comes to debugging time. I could choose "Log::Log4perl" as the role name, but that's not quite right because I don't handle everything: just the logging methods (
warn, etc). I might use
init_and_watch for something else, for example. What if my code could also dispatch to Log::Dispatch depending on the user configuration? Then I might claim I have the "Log::Dispatch" role.
But, does it matter if I have the Log4perl or Log::Dispatch role? Not really. I don't want to higher level to know how I do it, just that I can do it. I want them to know I have a "Logging" role, but not in the lumber sense, which also needs a
warn method. What about the *::Any modules? Should they be the canonical role name? I made my little piece and claimed it handled some role, but another CPAN author supports the same set of behaviors yet gave it another name.
I think, to be really useful, you don't want to check that an object does a role. You really want to check that it does the part of the role that you are interested in. You know that eventually roles will conflict, and one of them has to win. Even if the combined roles can dispatch to both, you're going to want to choose. Checking
can() leaves room for error:
if( $object->does( $role ) and $object->can( $method ) ) # it can, but is $method in $role?
You need to check both, in this fictional interface:
$object->does( $role, $method );
Or maybe return a Role object that you can query:
$object->does( $role )->can( $method );
In that case, if the role provided only part of its interface, you get the right answer when another role provided the same method. Not that we want to be able to have that, but you know it's going to happen eventually.
does() has many advantages because it does a lot more than
isa(), but it's merely the foundation of a good practice that we have yet to develop. It's going in the book as at least an
isa() replacement, but other than its default behavior, we're missing a lot that would make this useful beyond that.