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 (debug
, 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 does()
and 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.
Indeed, 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.
This is a complex problem and part of the problem is that Perl 5 does not have proper signatures and this effectively hides information from us. If we could inquire if $object->can(munge(HTML $str)), then we could mitigate this issue, but ultimately we can't know the intent of the code.
A true authority would certainly help with most of this issue because you and I could each release Role::Logging and people could ask:
$object->does( $role, $authority );
At the BBC, it turns out to be a non-issue because there are so few public roles that we don't worry about name clashes. When it happens, though, it will be a problem. Still, while it won't eliminate the false cognate problem, it should hopefully mitigate it because a role should be a much smaller chunk of code since it's only for cross-cutting behavior, not class responsibilities. Plus, roles should have a sensible name (I recommend putting Role:: in the namespace) to ensure that people know instantly what they are.
As for this:
if( $object->does( $role ) and $object->can( $method ) )
What if the role requires that method? Should can return true? What if the role provides that method but it's been excluded or renamed?
Fortunately, one piece of advice which can help is to realize that excluding or aliasing method names in roles is generally a code smell. Similar things should be similar and at the BBC, we found that if two roles provide the same method name but they do radically different things, the names were bad. If they did similar things, we generally had more refactoring to do and this eliminated the duplicate methods.