Role up! Role up! (but keep hold of your Moose and make your meta immutable)

So what’s this immutable stuff all about? In short, it’s the Moose equivalent of

use strict;
use warnings;

Summary: Stick

__PACKAGE__->meta->make_immutable;

at the bottom of every Moose class unless you really know what you’re doing. It makes your code safer (and faster).

If I explain it in detail we’ll lose perspective, so I’ll show you how I learnt this lesson as a case in point.

Static roles vs Dynamic roles

When declaring a Moose class, “MontyPerl” say, you can give it extra methods by assigning it a role with a call like

with MontyPerl::Lumberjack;

It sometimes happens that you will only find out on-the-fly which role it should have – after the object has been instantiated. That’s called a dynamic role.

Here is my first attempt at writing a class with a dynamic role. As a comment you’ll see the call to make_immutable I failed to add.

#!/usr/bin/env perl

# The Class
{
    package MontyPerl;

    use Moose;

    has sketch => (
        is       => 'ro',
        isa      => 'Str',
        required => 1,
    );

    sub info {
        my $self = shift;

        with 'MontyPerl::'.$self->sketch;
        return $self->sketch .': '. $self->quote;
    };

    #__PACKAGE__->meta->make_immutable;
}

# Role 1
{
    package MontyPerl::Lumberjack;

    use Moose::Role;

    sub quote { 'I cut down trees, I skip and jump, I like to press wildflowers.'; }
}

# Role 2
{
    package MontyPerl::EncyclopediaSalesman;

    use Moose::Role;

    sub quote { 'No madam, I'm a burglar, I burgle people.'; }
}

# Script
{
    use strict; use warnings; use feature qw/say/;

    say MontyPerl->new(  sketch => 'Lumberjack' )->info;
    say MontyPerl->new(  sketch => 'EncyclopediaSalesman' )->info;
}

Running it (with only the vaguest memory of the Flying Circus) you’ll see that the output is incorrect:

 $ ./monty.pl 
Lumberjack: I cut down trees, I skip and jump, I like to press wildflowers.
EncyclopediaSalesman: I cut down trees, I skip and jump, I like to press wildflowers.

Make the meta immutable

When you un-comment the make_immutable line and try to run it again, you’ll get a very long error message, which will inform you that:

The 'add_method' method cannot be called on an immutable instance at... 
[snip 3000 characters]
Moose::with('MontyPerl::Lumberjack') called at ./monty.pl line 18

Congratulations! You have successfully made your class immutable.

I want to come in and steal a few things, madam.

Now the thing is, you still want to give your object the role of EncyclopediaSalesman – and that’s the clue – the object should be given the role, not the class.

At the top of the package MontyPerl, add a line:

package MontyPerl;

use Moose;
use Moose::Util 'apply_all_roles';

and change the with-statement to

apply_all_roles($self, 'MontyPerl::'.$self->sketch);

This time when you run it, you’ll get

$ ./monty-good.pl
Lumberjack: I cut down trees, I skip and jump, I like to press wildflowers.
EncyclopediaSalesman: No madam, I'm a burglar, I burgle people.

Under the Hood

Well, you’re obviously curious about how the roles actually work. While the only way to find out is to read Moose::Meta::Role and Moose::Util::apply_all_roles here are some hints to get you going:

  • References to the methods of a class are stored in its meta-class.
  • A meta-class is an object rather than a class
  • Two objects of the same class have, by default, the same meta-class object. Modifying it affects both objects.
  • apply_all_roles assigns a new copy of the meta-class to the object before modifying it, therefore the other objects which have the original meta-class aren’t affected.

References

Print Friendly

Leave a Reply