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:
- you need at least perl 5.16. If you need to upgrade, consider perlbrew or plenv
- if you don’t have cpanm, get it with
curl -L http://cpanmin.us | perl - App::cpanminus
- first, we need to install twigils, with
cpanm --dev twigils
- if you’re using github, just fork the
p5-mop-redux project. Otherwise you can get a zip here.
- 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:
$self will be available directly, no need to shift @_.
- attributes variable will be available automatically, so you can access
attributes from within the class without having to use their
$self->accessors.
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:
- Classes can have a
BUILD method, as with Moose.
- A class can inherit from an other one by
extend-ing it.
- In a inheriting class, calling the parent method is not done using
SUPER,
but $self->next::method.
- A class
Foo declared in the package Bar will be defined as Bar::Foo.
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;
ro / rw means it’s read-only / read-write
lazy means the attribute constructor we’ll be called only when the
attribute is being used
weak_ref enables an attribute to be a weak reference
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:
- non-existent in the underlying hash
- present in the hash but with an undef value
- present and defined but false
- present, defined and true
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 {
# ...
}
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 ?).
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:
- Stevan Little
- Jesse Luehrs
- Toby Inkster
- Lukas Atkinson