Damien Krotkine home

p5-mop: a gentle introduction

I guess that you’ve heard about p5-mop by now.

If not, in a nutshell, p5-mop is an attempt to implement a subset of Moose into the core of Perl. Moose provides a Meta Object Protocol (MOP) to Perl. So does p5-mop, however p5-mop is implemented in a way that it can be properly included in the Perl core.

Keep in mind that p5-mop goal is to implement a subset of Moose, and. As Stevan Little says:

We are not putting “Moose into the core” because Moose is too opinionated, instead we want to put a minimal and less opinionated MOP in the core that is capable of hosting something like Moose

As far as I understood, after a first attempt that failed, Stevan Little restarted the p5-mop implementation: the so-called p5-mop-redux github project, using Devel::Declare, ( then Parse::Keyword ), so that he can experiment and release often, while keeping the implementation core-friendly. Once he’s happy with the features and all, he’ll make sure it finds its way to the core. A small team (Stevan Little, Jesse Luehrs, and other contributors) is actively developping p5-mop, and Stevan is regularly blogging about it.

If you want more details about the failing first attempt, there is a bunch of backlog and mailing lists archive to read. However, here is how Stevan would summarize it:

We started the first prototype, not remembering the old adage of “write the first one to throw away” and I got sentimentally attached to my choice of design approach. This new approach (p5-mop-redux) was purposfully built with a firm commitment to keeping it as simple as possible, therefore making it simpler to hack on. Also, instead of making the MOP I always wanted, I approached as building the mop people actually needed (one that worked well with existing perl classes, etc)

Few months ago, when p5-mop-redux was announced, I tried to give it a go. And you should too ! Because it’s easy.

Why is it important to try it out ?

It’s important to have at least a vague idea of where p5-mop stands at, because this project is shaping a big part of Perl’s future. IMHO, there will be a before and an after having a MOP in core. And it is being designed and tested right now. So as Perl users, it’s our chance to have a look at it, test it, and give our feedback.

Do we like the syntax ? Is it powerful enough? What did we prefer more/less in Moose ? etc. In few months, things will be decided and it’ll only be a matter of time and implementation details. Now is the most exciting time to participate in the project. You don’t need to hack on it, just try it out, and provide feedback.

Install it

p5-mop is very easy to install:

  1. you need at least perl 5.16. If you need to upgrade, consider perlbrew or plenv
  2. if you don’t have cpanm, get it with curl -L http://cpanmin.us | perl - App::cpanminus
  3. first, we need to install twigils, with cpanm --dev twigils
  4. if you’re using github, just fork the p5-mop-redux project. Otherwise you can get a zip here.
  5. using cpanm, execute cpanm . from within the p5-mop-redux directory.

A first example

Here is the classical point example from the p5-mop test suite

use mop;

class Point {
has $!x is ro = 0;
    has $!y is ro = 0;

    method set_x ($x) {
        $!x = $x;
    }

    method set_y ($y) {
        $!y = $y;
    }

    method clear {
        ($!x, $!y) = (0, 0);
    }

    method pack {
        +{ x => $self->x, y => $self->y }
    }
}

# ... subclass it ...

class Point3D extends Point {
    has $!z is ro = 0;

    method set_z ($z) {
        $!z = $z;
    }

    method pack {
        my $data = $self->next::method;
        $data->{z} = $!z;
        $data;
    }
}

This examples shows how straightforward it is to declare a class and a subclass. The syntax is very friendly and similar to what you may find in other languages.

class declares a class, with proper scoping. method is used to define methods, so no sub there. The distinction is important, because in methods, additional variables will be automatically available:

Functions defined with the regular sub keyword won’t have all these features, and that’s for good: it makes the difference between function and method more explicit.

hasdeclares an attribute. Attribute names are twigils. Borrowed from Perl6, and implemented by Florian Ragwitz in its twigils project on github, twigils are useful to differenciate standard variables from attributes variables:

class Foo {
    has $!stuff;
	method do_stuff ($stuff) {
        $!stuff = $stuff;
    }
}

As you can see, it’s important to be able to differenciate stuff (the variable) and stuff (the attribute).

The added benefit of attributes variables is that one doesn’t need to contantly use $self. A good proportion of the code in a class is about attributes. Being able to use them directly is great.

Other notes worth mentiong:

Attributes traits

When declaring an attribute name, you can add is, which is followed by a list of traits:

has $!bar is ro, lazy = $_->foo + 2;

Default value / builder

has $!foo = 'default value';

which is actually

has $!foo = sub { 'default value' };

So, there is no default value, only builders. That means that has $!foo = {}; will work as expected ( creating a new hashref each time ).

You can reference the current instance in the attribute builder by using $_:

has $!foo = $_->_init_foo;

There has been some comments about using = instead of // or || or default, but this syntax is used in a lot of other programing language, and considered somehow the default (ha-ha) syntax. I think it’s worth sticking with = for an easier learning curve for newcomers.

Class and method traits

UPDATE: Similarly to attributes, classes and methods can have traits. I won’t go in details to keep this post short, but you can make a class abstract, change the default behaviour of all its attributes, make it work better with Moose, etc. Currently there is only one method trait to allow for operator overloading, but additional ones may appear shortly.

Methods parameters

When calling a method, the parameters are as usual available in @_. However you can also declare these parameters in the method signature:

method foo ($arg1, $arg2=10) {
    say $arg1;
}

Using = you can specify a default value. In the method body, these parameters will be available directly.

Types

Types are not yet core to the p5-mop, and the team is questioning this idea. The concensus is currently that types should not be part of the mop, to keep it simple and flexible. You ought to be able to choose what type system you want to use. I’m particularly happy about this decision. Perl is so versatile and flexible that it can be used (and bent to be used) in numerous environment and configuration. Sometimes you need robustness and high level powerful features, and it’s great to use a powerful typing system like Moose’s one. Sometimes (most of the time? ) Type::Tiny (before that I used Params::Validate) is good enough and gives you faster processing. Sometimes you don’t want any type checking.

Clearer / predicate

Because the attribute builder is already implemented using =, what about clearer and predicate?

# clearer
method clear_foo { undef $!foo }

# predicate
method has_foo { defined $!foo }

That was pretty easy, right? Predicates and clearers have been introduced in Moose because writing them ourselves would require to access the underlying HashRef behind an instance (e.g. sub predicate { exists $self->{$attr_name}}) and that’s very bad. To work around that, Moose has to generate that kind of code and provide a way to enable it or not. Hence the predicateand clearer options. So you see that they exists mostly because of the implementation.

In p5-mop, thanks to the twigils, there is no issue in writing predicates and cleare ourselves.

But I hear you say “Wait, these are no clearer nor predicate ! They are not testing the existence of the attributes, but their define-ness!” You’re right, but read on!

Undef versus not set

In Moose there is a difference between an attribute being unset, and an attribute being undef. In p5-mop, there is no such distinction. Technically, it would be very difficult to implemente that distinction, because an attribute variable is declared even if the attribute has not been set yet.

In Moose, because objects are stored in blessed hashes, an attribute can either be:

That’s probably too many cases… Getting rid of one of them looks sane to me.

After all, we got this “not set” state only because objects are stored in HashRef, so it looks like it’s an implementation detail that made its way into becoming a concept on its own, which is rarely a good thing.

Plus, in standard Perl programming, if an optional argument is not passed to a function, it’s not “non-existent”, it’s undef:

foo();
sub foo {
    my ($arg) = @_; # $arg is undef
}

So it makes sense to have a similar behavior in p5-mop - that is, an attribute that is not set is undef.

Roles

Roles definition syntax is quite similar to defining a class.

role Bar {
    has $!additional_attr = 42;
    method more_feature { say $!additional_attr }
}

They are consumed right in the class declaration line:

class Foo with Bar, Baz {
    # ...
}

Meta

Going meta is not difficult either but I won’t describe it here, as I just want to showcase default OO programming syntax. On that note, it looks like Stevan will make classes immutable by default, unless specified. I think that this is a good idea (how many time have you written make_immutable ?).

My (hopefully constructive) remarks

Method Modifiers

Method modifiers are not yet implemented, but they won’t be difficult to implement. Actually, here is an example of how to implement method modifiers using p5-mop very own meta. It implements around:

sub modifier {
    if ($_[0]->isa('mop::method')) {
        my $method = shift;
        my $type   = shift;
        my $meta   = $method->associated_meta;
        if ($meta->isa('mop::role')) {
            if ( $type eq 'around' ) {
                $meta->bind('after:COMPOSE' => sub {
                    my ($self, $other) = @_;
                    if ($other->has_method( $method->name )) {
                        my $old_method = $other->remove_method( $method->name );
                        $other->add_method(
                            $other->method_class->new(
                                name => $method->name,
                                body => sub {
                                    local ${^NEXT} = $old_method->body;
                                    my $self = shift;
                                    $method->execute( $self, [ @_ ] );
                                }
                            )
                        );
                    }
                });
            } elsif ( $type eq 'before' ) {
                die "before not yet supported";
            } elsif ( $type eq 'after' ) {
                die "after not yet supported";
            } else {
                die "I have no idea what to do with $type";
            }
        } elsif ($meta->isa('mop::class')) {
            die "modifiers on classes not yet supported";
        }
    }
}

It is supposed to be used like this:

method my_method is modifier('around') ($arg) {
    $arg % 2 and return $self->${^NEXT}(@_);
    die "foo";
}

I would like to see method modifiers in p5-mop. As per Stevan Little and Jesse Luehrs, it may be that these won’t be part of the mop, but in a plugin or extension. I’m not to sure about that, for me method modifier is really linked to OO programmning. I prefer using around than fiddling with $self->next::method or ${^NEXT}.

Here are some syntax proposals I’ve gathered on IRC and blog comments regarding what could be method modifiers in p5-mop:

around foo { }
method foo is around { ... }
method foo is modifier(around) { ... }

${^NEXT} and ${^SELF}

These special variables are pointing to the current instance (useful when you’re not in a method - otherwise $self is available), and the next method in the calling chain. It’s OK to have such variables, but their horrible name makes it difficult to remember and use.

Can’t we have yet an other type of twigils for these variables ? so that we can write $^NEXT and $^SELF.

Twigils for public / private attributes

Just an idea, but maybe we could have $!public_attribute and $.private_attribute. Or is it the other way around ?

why is ? we already have has !

This one thing is bothering me a lot: why do we have to use the word is when declaring an attribute? The attribute declaration starts with has. So with is, that makes it two verbs for one line of code. For me it’s too much. in Moo* modules, the is was just one property. We had default, lazy, etc. Now, is is just a seperator between the name and the ‘traits’. In my opinion, it’s redundant.

Also, among the new keywords added by p5-mop, we have only nouns (class, role, method). Only one verb, has.

The counter argument on this is that this syntax is inspired by Perl6:

class Point is rw {
    has ($.x, $.y);
    method gist { "Point a x=$.x y=$.y" }
}

So, “blame Larry” ? :)

Exporter

p5-mop doesn’t use @ISA for inheritance, so use base 'Exporter' won’t work. You have to do use Exporter 'import'. That is somewhat disturbing because most Perl developers (I think) implement functions and variables exporting by inheriting from Exporter (that’s also what the documentation of Exporter recommends).

You could argue that one should code clean classes (that don’t export anything, and clean modules (that export stuff but don’t do OO). Mixing OO in a class with methods and exportable subs looks a bit un-orthodox. But that’s what we do all day long and it is almost part of the Perl culture now. Think about all the modules that provides 2 APIs, a functional one and an OO one. All in the same namespace. So, somehow, being able to easily export subs is needed.

However, as per Jesse Luehrs and Stevan Little, they don’t think a MOP implementation should be in charge of implementing an Exporter module, and I can totally agree with this. So it looks like the solution will be a method trait, like exportable:

sub foo is exportable { ... }

But that is not yet implemented.

Inside Out objects versus blessed structure objects

p5-mop is not using the standard scheme where an object is simply a blessed structure (usually a HashRef). Instead, it’s using InsideOut objects, where all you get as an object is some kind of identification number (usually a simple reference), which is used internally to retrieve the object properties, only accessible from within the class.

This way of doing may seem odd at first: if I recall correctly, there a time where InsideOut objects were trendy, especially using Class::Std. But that didn’t last long, when Moose and its follow ups came back to using regular blessed structured objects.

The important thing to keep in mind is that it doesn’t matter too much. Using inside out objects is not a big deal because p5-mop provides so much power to interact and introspect with the OO concepts that it’s not a problem at all that the attributes are not in a blessed HashRef.

However, a lot of third-party modules assume that your objects are blessed HashRef. So when switching to p5-mop, a whole little ecosystem will need to be rewritten.

UPDATE: ilmari pointed out in the comments that there is a class trait called repr that makes it possible to change the way an instance is implemented. You can specify if an object should be a reference on a scalar, array, hash, glob, or even a reference on a provided CodeRef. This makes p5-mop objects much more compatible with the OO ecosystem.

Now, where to ?

Now, it’s your turn to try it out, make up your mind, try to port an module or write on from scratch using p5-mop, and give your feedback. To do that, go to the IRC channel #p5-mop on the irc.perl.org server, say hi, and explain what you tried, what went well and what didn’t, and how you feel about the syntax and concepts.

Also, spread the word by writing about your experience with p5-mop, for instance on blogs.perl.org.

Lastly, don’t hesitate to participate in the comments below :) Especially if you don’t agree with my remarks above.

Reference / See also

Contributors

This article has been written by Damien Krotkine, but these people helped proof-reading it:

blog comments powered by Disqus
Fork me on GitHub