p5-MOP gets p6 style traits (sorta)

So the other day in #p5-mop we were discussing how to handle meta layer extensions. For example, doing things like adding accessor generation support to attributes.

class Foo {
    has $foo ( is => 'ro' );
}

The traditional approach found in the old p5-mop and in Moose was to do this by subclassing things in the metaclass or applying roles to the existing metaobjects (as is the favored approach with Moose). But to be honest, this approach is kind of tedious and often ends up requiring a lot of subclassing and mucking about.

So, in the grand tradition of stealing cool stuff from Perl 6 for use in Perl 5, we did just that (kinda sorta).

Perl 6 has a concept of traits, which are typically applied with the keyword is (it's not technically a keyword, it is some crazy p6 deep magic, but that explanation is outside the scope of this blog post) and look like this:

sub fib is cached { ... }
my $key is Persistent(:file<.key>);

The idea is that the "argument" to the trait actually points to an in-scope subroutine (actually it is more likely a multi-sub, but again, p6-guts are out of scope here), and then that subroutine will alter the value they are applied to by poking at its internals. This is a fabulously flexible way to handle extensions of functionality without requiring too much knowledge of the interconnected workings of the underlying meta-layers.

This approach is actually something Jesse Luehrs was advocating even in the original p5-mop project, and when he advocated for it again I decided to give it a try. And again, I am happy to say that not only did it work, but it ended up requiring very little code changes. Most of the work was in the parser code and then writing the actual traits themselves. I believe that this actually greatly reduces the complexity of extending the MOP because all it requires is for the user to write a subroutine.

So as of about an hour ago, I have added three traits to the core of p5-mop as a starting point. This means that the following code ...

class Person {
    has $first_name is ro;
    has $last_name is rw;
}

Will generate a read-only accessor for the $first_name attribute and a read-write accessor for the $last_name attribute.

I also implemented the abstract trait for classes, which will mark the given class as explicitly abstract. Along with this, I also changed the previous implicit abstract behavior that would result if a class had an unfulfilled required method, and made it die upon class compile time. This means that this code ...

class Foo {
    method bar;
}

Will now die at compile time unless you alter it to be ...

class Foo is abstract {
    method bar;
}

... which will compile just fine (although you won't be able to create instances of the class since it is abstract).

From here I am going to work on cleaning up the parser and the method dispatcher (MRO) a little (they got kinda messy in the latest rush of features) but then I think we can call this 0.01.

5 Comments

One of the nice things about the way Moose does it now is that you can write a single MX that affects many meta-things. In p5-mop, this would translate to something like ...


class Foo is complex {
    has $bar is ro; # also complex

method baz { ... } # also complex
}

I hope we don't have to repeat "is complex" over and over with p5-mop.

If you look at the examples:

https://github.com/stevan/p5-mop-redux/blob/master/lib/mop/traits.pm

It looks like the trait implementations get passed the metaclass as the first argument. So it seems like you could modify other attributes and methods that belong to that metaclass.

The idea is that the "argument" to the trait actually points to an in-scope subroutine

I would expect it to result in some metaclass method being called.

I would expect it to result in some metaclass method being called.

But then you need to create a subclass of the metaclass which implements the method in question. And if you've got multiple traits, then normal subclasses are probably out of the question, and you need to instead build a subclass by composing various roles. This is what people do with Moose, and it's one of the things Stevan's trying to avoid for p5-mop.

That said, the sub that gets called does get passed the metaclass as an argument, so it may apply a role to it, or call a method on it, or whatever.

Leave a comment

About Stevan Little

user-pic I blog about Perl.