Friday, December 4, 2009

Simplifying BEGIN { } with Moose roles

This is a common Perl pattern:

package MyClass;
use Moose

use Try::Tiny;

use namespace::autoclean;

BEGIN {
    if ( try { require Foo; 1 } ) {
        *bar = sub {
            my $self = shift;
            Foo::foo($self->baz);
        };
    } else {
        *bar = sub {
            ... # fallback implementation
        };
    }
}

However, since this is a Moose class there is another way:

package MyClass;
use Moose

use Try::Tiny;

use namespace::autoclean;

with try { require Foo; 1 }
    ? "MyClass::Bar::Foo"
    : "MyClass::Bar::Fallback";
package MyClass::Foo;
use Moose::Role;

use Foo qw(foo);

use namespace::autoclean;

sub bar {
    my $self = shift;

    foo($self->baz);
}
package MyClass::Bar::Fallback;
use Moose::Role;

use namespace::autoclean;

sub bar {
    ...; # fallback implementation
}

Obviously for something that simple it doesn't make sense, but if there is more than one method involved, or the fallback implementation is a little long, it really helps readability in my opinion. Going one step further, you can create an abstract role like this:

package MyClass::Bar::API;
use Moose::Role;

use namespace::autoclean;

requires "bar";

and add it to the class's with statement to validate that all the required methods are really provided by one of the roles.

Role inclusion is usually thought of as something very static, but dynamism can be very handy without doesn't hurting the structure of the code.

If you want to be pedantic the role inclusion is not at compile time, but the loading of Foo is done at compile time inside the role (Foo is usually why it was in a BEGIN block in the first place, in most of the code I've seen).

2 comments:

Piotr said...

You can do:

use maybe 'Foo';

with maybe::HAVE_FOO
? "MyClass::Bar::Foo"
: "MyClass::Bar::Fallback";

The maybe::* is a constant value so this statement will be optimized by compilator.

nothingmuch said...

It only ever runs once, so it doesn't need to be optimized per se, but 'maybe' looks like a very handy module nonetheless =)