If I were being accurate, this entry would actually be titled “What’s Wrong With Perl 5′s Overloading for People Who Care About Defensive Programming?” If you don’t care about defensive programming, then Perl 5′s overloading is perfect, and you can stop reading now. Also, please let me know so I can avoid working on code with you, thanks.
Defensive programming, for the purposes of this entry, can be defined as “checking sub/method arguments for sanity”. Yes, there’s more to it, but that’s where Perl 5′s overloading fails so badly.
Let’s say I’m writing an API that wants to take a filename as argument. What types of things might I expect? Well, obviously, I’d expect a string. More specifically, I’d expect a non-empty string, since q{} isn’t a valid filename. I could go further and even require a valid filename for my file system, but a non-empty string is a good baseline.
Enter Path::Class. This is a very handy module that gives you objects to represent directory and file names. It also uses Perl 5′s overloading to stringify when it’s used as a string. Genius, that’s exactly what I want.
So how exactly can I allow both strings and Path::Class::File objects as an argument? Well, I can start by checking if the argument has a string value. How do I do that, you ask? Well, you can’t, and that’s the problem! Everything in Perl stringifies, so if I pass any object, or any reference, or even undef, they will all turn into a string when used in a string context, although with undef I might get a warning.
In an ideal world, string-ishness would be represented as a method on an object, like as_string(). Unfortunately, Perl’s builtin types are not objects, so that’s right out. The only way that I know of to check if something can act as a string intentionally is the following hideous bit of code:
if (
defined $val
&& ( !ref $val
|| ( blessed $val && overload::Method( $val, q{""} ) ) )
) {
...;
}
That’s a disgusting mouthful of gibberish. The turd cherry on top of that shit sundae of code is that the only way to accomodate overloading is to explicitly check for it. This completely violates the purpose of overloading, making things transparently act like builtin types!
There are alternatives. For example, I could check for a Path::Class::File object and use Moose’s coercion feature (along with MooseX::Params::Validate to coerce a bare string to an object. But if there’s some other class of object that stringifies as a file name then we have to coerce that explicitly, or use a union type.
This broken-ness really only applies to certain builtin operations, like stringification, numification, comparison, etc. The underlying problem is that all of Perl’s builtin types “work” with these operations. Other types of overloading, like overloading array dereferencing, are just fine. I can just write eval { @{ $val } } and if it works I know I have something that acts like an array reference.
This is, I think, fixed in Perl 6. It has an explicit API for typecasting so if a class wants to support stringification, it implements a Str() method. From what I can see, builtins like Array and Hash don’t implement this typecasting (yay). Good job Perl 6 team!
For now, I guess I’m stuck with the nastiness up above, or just throwing up my hands and saying “stringify your arguments your own damn self”.