A recent thread on p5p about warnings from Pod::Abstract with Perl 5.11.3 brought up an old debate over the nature of the UNIVERSAL
package in Perl 5.
A recent deprecation of importing isa
and can
as functions from UNIVERSAL
is the source of the warnings. This has the potential to affect plenty of code, because plenty of existing Perl 5 code does the wrong thing in the wrong way.
That's not entirely the fault of the coders; there's almost no right way to ask the right question in the right way. This is a design flaw in Perl 5.
The problem is that you can't always be sure that a scalar you have supports the operations you want to perform on it. For example, suppose that you receive as a parameter an object that may or may not allow logging with the log()
method. If you call log()
on it, you may get an exception.
One approach is to call can()
on the object to see if Perl 5 knows about a log()
method defined on the object:
if ($obj->can( 'log' ))
{
...
}
That's all well and good (except that it is susceptible to the
false cognate problem) until $obj
may not in fact be a valid
object at all. In that case, trying to call a method on a non-invocant may not
work.
I don't know why you'd write an API where this is possible, but apparently it's a popular hobby.
Some people think that the proper approach is to avoid the method call altogether. I used to think this too. I was wrong. Here's the broken code they write:
if (UNIVERSAL::can( $obj, 'log' )) # broken code; do not use
{
...
}
The warning introduced in 5.11.3 by the linked patch doesn't catch this case. (You have to use the shouldn't-be-so-controversial UNIVERSAL::can module to get warnings in this case.) It only catches the case where someone has written:
use UNIVERSAL 'can'; # broken code; do not use
if (can( $obj, 'log' )) # broken code; do not use
{
...
}
The safest approach is to change the API so that you never have to guess if you have a valid invocant. If you can't do that, the safest check is:
if (eval { $obj->can( 'log' ) })
{
...
}
... to catch the exception (or use Try::Tiny).
This is a pattern repeatable with isa
as well.
As mentioned earlier, this still leaves the code open to false cognate problems, where the mere presence of a method (or function) named log()
is not sufficient to determine whether that method (or function) actually behaves as expected.
This is the purpose of the DOES()
method added in Perl 5.10 — if you use roles instead of duck typing.
Why is it unsafe to call can()
as a function?
AUTOLOAD()
. The implementation of can()
in
UNIVERSAL
has no idea if any class or object will generate its own
methods. It only knows how to look in the appropriate namespace for CVs; it
can't even distinguish between functions and methods. Of course, if you write
your own AUTOLOAD
for a class, you must also override the
can()
method to return the appropriate sub reference when queried.
Code that neglects to do so — far too much code, in truth — is also
broken. (Then you can have the fun experience of debugging other modules which
call can()
as a function and break your carefully-written
objects.)
Things get trickier when dealing with isa()
; more about that next time.