January 17, 2009

Parametric Roles in Perl 5

Jonathon Worthington just wrote about Perl 6's new support for parametric roles. Excellent!

Moose supports this feature too, through the MooseX::Role::Parameterized extension I wrote a little over a month ago. It has proved to be a very useful pattern. I'm pleased that Perl 6 has it built in.

Out of curiosity, I ported the examples Jonathon provided to MXRP. I'll have to work more with rafl to make MooseX::Declare support something resembling Perl 6's much nicer syntax. We do plan on having syntax for positional parameters in much the same way Perl 6's parametric roles do.

package Greet;
use MooseX::Role::Parameterized;

parameter greeting => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

role {
    my $p = shift;
    my $greeting = $p->greeting;

    method greet => sub {
        print "$greeting!\n";
    };
};

package EnglishMan;
use Moose;
with Greet => { greeting => "Hello" };

package Slovak;
use Moose;
with Greet => { greeting => "Ahoj" };

package Lolcat;
use Moose;
with Greet => { greeting => "OH HAI" };

EnglishMan->new->greet; # Hello!
Slovak->new->greet; # Ahoj!
Lolcat->new->greet; # OH HAI!

I'll skip the second example because it's contained by the third example.

Moose doesn't give you multiple dispatch. sigh! Instead we model the problem with a default value for the transform, which is a code reference. In this way we meet the original requirement of EnglishMan and Lolcat not needing to provide a nominative->accusative transform.

package Request;
use MooseX::Role::Parameterized;

parameter statement => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

parameter transform => (
    is      => 'ro',
    isa     => 'CodeRef',
    default => sub {
        sub { $_[0] }, # identity function
    },
);

role {
    my $p = shift;
    my $statement = $p->statement;
    my $transform = $p->transform;

    method request => sub {
        my ($self, $object) = @_;
        print "$statement " . $transform->($object) . "?\n";
    };
};

package Language::Slovak;
sub accusative {
    my $nom = shift;
    (my $acc = $nom) =~ s/a$/u/;
    return $acc;
}

package EnglishMan;
use Moose;
with Request => { statement => "Please can I have a" };

package Slovak;
use Moose;
with Request => {
    statement => "Prosim si",
    transform => \&Language::Slovak::accusative,
};

package Lolcat;
use Moose;
with Request => { statement => "I CAN HAZ" };

EnglishMan->new->request("yorkshire pudding");
Slovak->new->request("boravicka");
Lolcat->new->request("CHEEZEBURGER");

I have no conclusion, except that Perl 5 hasn't been stagnant, though its new features can be a lot more verbose than in Perl 6.