NAME
mop::manual::tutorials::moose_to_mop - A manual for p5-mop
DESCRIPTION
This tutorial is an attempt to map some of the common Moose concepts to their MOP equivalents.
Creating Classes
In Moose, a class is just a package which contains a use Moose declaration. After that, you use the Moose keywords: extends introduces inheritance, with adds roles, and has creates attributes in the class. After this, any subroutines created in the package are automatically added as methods of the class.
In the MOP there are explicit class, method, and attribute declarations. Here is a simple example:
class Foo extends Bar with Baz, Gorch {
has $!foo;
has $!bar;
method foo { ... }
method set_bar ($b) { ... }
}
This creates a class named Foo which is a subclass of the Bar class and consumes the Baz and Gorch roles. It contains two attributes, $!foo and $!bar, and two methods, foo and set_bar. set_bar takes one argument, $b.
The specific details of attributes and methods will be discussed later, but the above example should provide a basic overview of the base syntax.
Constructing instances
Classes which do not explicitly extend a superclass will automatically extend the mop::object class. This class will provide a new method which can be used to construct instances.
The new method inherited from mop::object accepts a set of key/value pairs where the key is the name of an attribute (minus the sigil). The instances created by the MOP are opaque, meaning they are not HASH refs, and access to the individual slots is only possible either inside a method (where they are accessible as normal variables) or via the MOP (the details of which are left as an exercise to the reader).
BUILDARGS
If you wish to override the default constructor's behavior with regard to arguments, then you simply create a new method yourself to accept different parameters, then munge those parameters into key/value pairs before calling the mop::object constructor with next::method.
This is very similar to overriding the BUILDARGS method in a Moose class. Here is a short example:
class Foo {
has $!bar;
method new ($class: $b) {
$class->next::method( bar => $b );
}
}
It should be noted that any attempt to assign to an attribute will result in an error. So if you either need access to $self or attributes, you can do this inside of the BUILD method, which will be automatically called from new.
BUILD
As with Moose, if you need to perform initialization on an instance, you can use the BUILD method for that.
Here is a simple example where the bar argument to the constructor is not actually stored, but instead is processed first, then stored in the $!double_bar attribute.
class Foo {
has $!double_bar;
method BUILD ($args) {
$!double_bar = $args->{'bar'} * 2
if exists $args->{'bar'};
}
}
Creating Attributes
Below is the list of options for the Moose has keyword. With each of them we will show an example of how to accomplish the same with the MOP.
- is => 'rw'|'ro'
-
This is supported by the core
roandrwtraits, which are applied with theismodifier. Here is a simple example:class Foo { has $!bar is ro; has $!baz is rw; } - isa => $type_name
-
There is currently no support for types in the MOP. It is possible, however, to create a
typetrait that can perform the same validation, and even use Moose type constraints. See t/400-traits/003-type-trait.t for an example. - coerce => (1|0)
-
As was said with the
isaoption, there is no support for types in the MOP. However it would be possible to extend thetypetrait in t/400-traits/003-type-trait.t to also do coercion. It might look like this:class Foo { has $!bar is type( isa => 'SomeType', coerce => 1 ); }Actual implementation of this is left as an exercise for the reader.
- does => $role_name
-
This option is really just a shortcut for a type assignment so the suggestions in the above two options apply here as well.
- required => (1|0)
-
Required attributes are easily mimicked through the MOP without even needing a trait. Here is how they are accomplished:
class Foo { has $!bar = die '$!bar is required'; }When an instance of
Foois created and no value is supplied for$!bar, then the default will be executed, which in this case will simply die with the error message. - weak_ref => (1|0)
-
Weak references are accomplished using the
weak_reftrait that is core in the MOP. Here is a simple example:class Tree { has $!parent is weak_ref; } - lazy => (1|0)
-
Lazy attributes are supported via the core
lazytrait. Here is a simple example of that:class Foo { has $!bar is lazy; }Unlike Moose, where lazy attributes are only re-created if the slot has not been populated, in MOP they will be populated both if the slot has not been populated and if the value in the slot is undef. This is more in line with how regular scalars work, meaning that if you define a scalar with
my $foo;then it is implicitly undef and not some special "not assigned to" value. - trigger => $code
-
There is no specific way to do
triggeryet, but take a look at t/400-traits/020-moose-traits.t for a naive (and mostly wrong) version. - handles => ARRAY | HASH | REGEXP | ROLE | ROLETYPE | DUCKTYPE | CODE
-
There is no specific way to do
handlesyet, but take a look at t/400-traits/020-moose-traits.t for a simple version that supports the basic HASH syntax.NOTE: This will very likely be put into the core traits, but for now it is not.
- traits => [ @role_names ]
-
This is not currently supported, and honestly, probably never will be, since it is highly Moose specific.
- builder => Str
-
There is no special trait for
builder. Instead, you simply use the existing syntax for assigning default values to attributes and just call a method. Here is an example:class Foo { has $!bar = $_->build_bar; method build_bar { ... } }The standard perl topic variable (
$_) is localized to be the current instance when running the builders, so you can use it to access the current invocant. - default => SCALAR | CODE
-
There is also no special trait for
default. Instead you use the existing syntax. Here is how you would provide a simple default value:class Foo { has $!bar = 10; has $!baz = {}; }Pretty much anything you can stick in a scalar variable can go on the right hand side of a
hasexpression.If you need to initialize a more complex value, but for some reason do not want to use the
builderstyle approach, you can wrap your default value in adoblock, like so:class Foo { has $!bar = do { $_->bar_has_been_touched; 100; }; }While this works, most often it is better to use the
builderapproach. - clearer => Str
-
There is no special trait for
clearer. Instead it is recommended that you write a simple method that follows this pattern instead.class Foo { has $!bar; method clear_bar { undef $!bar } }If
$!barwas lazy, this would force a recalculation of$!barthe next time that$!barwas accessed. - predicate => Str
-
There is no special trait for
predicate. Instead it is recommended that you write a simple method that follows this pattern instead.class Foo { has $!bar; method has_bar { defined $!bar } }It should be noted that if
$!barwas lazy, this would force evaluation of$!bar. If you want to test if a lazy value is yet to be initialized, you need to go through the MOP to get that. Here is what that code would look like.mop::meta($self)->get_attribute('$!bar')->has_data_in_slot_for($self)The real lesson here is that if you want lazy and predicates, you should implement the accessors yourself. See t/001-examples/003-binary-tree.t for an example of that.
- documentation => $string
-
This is not currently supported in the MOP and likely won't be. To the best of my knowledge it was hardly ever actually used in Moose.
BUGS
Since this module is still under development we would prefer to not use the RT bug queue and instead use the built in issue tracker on Github.
Git Repository
Issue Tracker
AUTHOR
Stevan Little <stevan.little@iinteractive.com>
Jesse Luehrs <doy@tozt.net>
COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by Infinity Interactive.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.