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
ro
andrw
traits, which are applied with theis
modifier. 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
type
trait 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
isa
option, there is no support for types in the MOP. However it would be possible to extend thetype
trait 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
Foo
is 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_ref
trait 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
lazy
trait. 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
trigger
yet, 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
handles
yet, 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
has
expression.If you need to initialize a more complex value, but for some reason do not want to use the
builder
style approach, you can wrap your default value in ado
block, like so:class Foo { has $!bar = do { $_->bar_has_been_touched; 100; }; }
While this works, most often it is better to use the
builder
approach. - 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
$!bar
was lazy, this would force a recalculation of$!bar
the next time that$!bar
was 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
$!bar
was 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.