The idea of creating a
Metaobject Protocol (
MOP ) for Perl 5 has been
around for a long time. Well known is one of the implementations -
Class :: MOP , which is used in
Moose . But getting into the basic delivery of Perl 5 can only be such a solution that will be compatible with the existing object model and will not be overloaded with excessive features and dependencies. The other day,
Stevan Little published the first trial release on CPAN of a possible candidate for this vacant position - the
mop module. The project went through a long evolution, the process was closely watched by the community. Let's take a look at what happened and what consequences this might have for Perl 5.
What is MOP?
Metaobject Protocol (Meta-Object Protocol) is a software interface for managing a system of objects in a language. Just as an object is an instance of a class, the class itself is represented by an object (or metaobject) with programmatically defined properties (attributes, methods, etc.). As Stevan Little put it, explaining to his mom what Moose is doing:
This is an abstraction for the abstraction system used to create abstractions.
When dealing with classes in basic Perl, we all came across a very simple MOP implementation:
no strict 'refs'; *{$foo . '::bar'} = \&baz;
This crypto-record in the course of the program operation adds the method
bar
for the class specified in the
$foo
variable, which is a reference (alias) to the
baz
function. If someone is familiar with the term
reflections , then this is also one of his examples. The difference between MOP and reflections is that it provides a convenient and flexible developer tool for metaprogramming classes without the need for semi-legal hacking techniques. For example:
class Foo { method bar { baz } }
Why do I need a MOP in the base Perl distribution?
If you look at the classic MOP book - “The Art of the Metaobject Protocol”, which described the implementation of the meta protocol in
CLOS , one of the main prerequisites for creating a protocol was the need to solve the problem of a variety of Lisp object system implementations that were incompatible with each other. The metaprotocol allowed building a compatible, flexible, and extensible object system that would satisfy all Lisp programmer requests.
')
A simple and non-dependency implementation of MOP in the base Perl package can unambiguously resolve the issue of choosing an object system to use when creating programs. This will end all religious wars in the confrontation of OOP frameworks and make it customary to connect MOPs like
use strict
.
The implementation of MOP for basic Perl should be easy and fast enough, for this reason the same Moose can never get there, but mop has every chance for that.
A syntactically beautiful and logical object system can inspire a lot of modules for rework, the authors of which did not dare to use any OOP frameworks due to their severity or excessive dependencies, which in turn will significantly improve the readability of the code and reduce the cost of supporting it (single style , simplified static code analysis).
Naturally, this can attract young developers to Perl, since simplicity and conciseness are very important when mastering a language that claims to be object-oriented.
As some people in the community note, it is likely that the MOP will become an important part of the future of Perl and there will be a clear boundary in the history of Perl: before and when the MOP arrives at the core.
Mop evolution
The development of the mop module was preceded by a very long way. Stevan Little after participating in the work on the object system for Perl 6 in the
Pugs compiler, decided to transfer the results to Perl 5. After several attempts, the Class :: MOP module appeared, which became the basis for creating Moose. Moose became very popular because it gave developers the tool for working with classes that they had been waiting for. But for a long time the start of programs on Moose and a large tree of dependencies scared potential users. So two years ago, Stephen caught fire with the idea of creating a compact MOP system that could be included in the basic delivery of Perl. This is how the
p5-mop project came
about , but it was crushed by the weight of its own complexity, which somewhat disappointed Stephen and led him to an unexpected experiment — the
Moe project, the implementation of the Perl 5 compiler in the
Scala language.
Some time passed and Steven decided that he needed to give the p5-mop a second chance and created the
p5-mop-redux project , which did not try to embrace the immense and did not set out to drag all the possibilities of Moose, but only become the basis for an expandable object system core perl.
Stephen kept the community up to date on work progress and published articles on the
blogs.perl.org blog platform. On October 15, the first trial release of the module on CPAN was published. Now the module has two active developers:
Stevan Little and
Jesse Luehrs , as well as several contributors.
Work with mop
Installation
To install mop, you can use cpanm:
$ cpanm --dev mop
Please note that mop makes extensive use of new Perl features such as the
Perl parser API . The minimum version of Perl required for the module to work is 5.16.
First example
Consider sample code using mop:
use mop; class Point { has $!x is ro = 0; has $!y is ro = 0; method clear { ($!x, $!y) = (0,0); } }
The class is defined by the
class
keyword. First, the
Point
class is declared, in which attributes are defined using the has keyword. Note that attribute variables are set using
twigil (
twigil or two-character
sigil ) to distinguish them from ordinary variables. It almost completely copies the syntax of Perl 6. At the moment, only
$!
tvigil attributes are supported
$!
which are private attributes.
After specifying twigil,
traits are followed by the keyword
is
. For example,
ro
means that the attribute is read only. After the equal sign, the default attribute value is set. In the
Point
class, the only
clear
method is set, which resets attribute values. It can be seen that the attribute variables
$!x
,
$!y
are available inside the methods as lexical variables in the scope of this class. Their values are locked within each instance of the class.
class Point3D extends Point { has $!z is ro = 0; method clear { $self->next::method; $!z = 0 } }
It defines the
Point3D
class, which becomes the heir of the Point class using the
extends
. Thus, the resulting class takes over all the attributes and methods of the class
Point
. In addition to them in the class attribute is set
$!z
The
clear
method is also redefined, which, as shown in the listing, calls the next (in the inheritance hierarchy) parent
clear
method from the
Point
class using
next::method
. In addition, the variable
$self
automatically defined inside each method, which is a reference to the current object.
In case no classes are specified for inheritance, the class is inherited from the
mop::object
class by default. This is demonstrated in the following example:
print Dumper mro::get_linear_isa(ref Point->new); $VAR1 = [ 'Point', 'mop::object' ];
Object attributes can be set when creating an instance of a class:
my $point = Point->new( x => 1, y => 1);
Note that we did not specify the
new
method for the
Point
class. This method is inherited from the
mop::object
class.
To access the attribute value, a getter method is automatically created, for example:
my $point = Point->new( x => 1, y => 1); print $point->x
The value
1
will be displayed. Since the attribute is declared as
ro
, an attempt to change it will lead to a runtime error:
$point->x(2); Cannot assign to a read-only accessor
However, inside the methods we can freely change any attributes, for example, we can create a
set_x
setter
set_x
:
class Point { has $!x is ro = 0; ... method set_x( $x=10 ) { $!x = $x } }
In this example, you can also clearly see how you can set the method signature, i.e. describe the variable arguments passed to the method, and even set the default values if the argument is omitted.
$point->set_x(5);
At the same time, attributes are in scope only for the class Point, i.e. we cannot directly operate with them in the class of the successor
class Point3D extends Point{ ... method set_x_broken { $!x = 10; } }
This will
No such twigil variable $!x
compilation error:
No such twigil variable $!x
Roles
Roles allow you to flexibly arrange classes with the necessary methods and attributes, while avoiding multiple inheritance.
role BlackJack { method win; method loose ($value) { not $self->win($value) } }
This role defines the two methods
win
and
loose
. The
win
method has no body, so in this case the method must be defined in the class that plays the role. In this respect, the role is similar to the concept of an interface present in other programming languages.
Now you can build a class with this role using the with keyword:
class LunaPark with BlackJack { method win ($value){ 0 } }
A class can be composed of several roles, in this case the names of roles are separated by commas.
Attribute Properties and Values
has $!foo is rw, lazy = 0
Attribute properties are separated by a comma, after the keyword is. The following properties are currently supported:
ro
/ rw
- read only / read / write accesslazy
- creating an attribute when it is first accessedweak_ref
- attribute is declared as “weak” link
When specifying the default value, it should be remembered that in fact, after the equal sign, there is an executable construct, i.e. record:
has $!foo = "value"
Means
has $!foo = sub { "value" }
Those. in essence, it is a function for creating a value, and not for assigning a given expression.
When setting the default value, you can refer to the current object instance using the $ _ variable:
has $!foo = $_->set_foo()
If it is required that when creating an object with the help of
new
, a certain attribute is necessarily set, the default value for it can be the following code:
has $!foo = die '$!foo is required';
Accordingly, if the value of the
foo
attribute is not specified when the object is created, an exception will occur.
Checks of types, classes of attributes and methods are not implemented in order not to overload the mop kernel with excessive complexity. However, such checks can easily be performed as an external function:
sub type {
The working implementation of the
type
function can be seen in the
example for the mop module.
Module creation
A typical module file might look like this:
package Figures; use strict; use warnings; use mop; our $debug = 1; sub debug { print STDERR "@_\n" if $debug; } class Point { has $!data is ro; method draw_point { debug("draw point") } method BUILD { $!data = "some data"; } method DEMOLISH { undef $!data; } } class Point3D extends Figures::Point {} my $point = Figures::Point3D->new; $point->draw_point;
As you can see from the example, if the
package
is specified, the class names get the corresponding prefix. In addition, the class is accessed by the module scope. This means that functions and variables defined in the module scope are also available for use inside classes. Moreover, such functions do not become class methods. No more pollution of the namespace with exported functions!
The special
BUILD
method can be used if some initialization is required when creating the object. This is convenient and allows you to not override the
new
method.
The
DEMOLISH
method
DEMOLISH
called when an object is destroyed, i.e. is a destructor.
The internal structure of the object in the mop
The object created by mop is not familiar to many
blessed hash references. Instead, so-called
InsideOut objects are used, where the entire internal structure is hidden in the class code and is accessible only through special methods. There are several public methods in mop that allow you to inspect the internal structure of an object:
mop::dump_object
- object dump
print Dumper mop::dump_object( Point3D->new ) $VAR1 = { '$!y' => 0, 'CLASS' => 'Point3D', '$!x' => 0, 'ID' => 10804592, '$!z' => 0, 'SELF' => bless( do{\(my $o = undef)}, 'Point3D' ) };
mop::id
- a unique identifier of the object
print mop::id(Point3D->new) 10804592
mop::meta
- meta-information about an object, for detailed introspection of objects
print Dumper mop::dump_object( mop::meta( Point3D->new ) ) $VAR1 = { '$!name' => 'Point3D', ...
mop::is_mop_object
is a logical function, returns true if the object is mop::object
print mop::is_mop_object( Point3D->new ) 1
Practical use
The mop module is currently undergoing active testing by the community. The basic recommendation is to take any module and try to rewrite it using mop. What mistakes and problems will you face? Write about it, it will be a great help in the further development of the project. For example, the
Plack module was successfully ported, all of which 1152 tests were successfully passed.
Now it is difficult to say whether mop will be accepted into the core distribution of Perl. If accepted, starting with which version: 5.20, 5.22 or later? This is unknown, but the overall very positive background around the event is encouraging.
Sources
- "Perl 5 MOP" by Stevan Little
- "Why Perl 5 Needs and Metaobject Protocol" by chromatic
- "P5-mop, a gentle introduction" by Damien "dams" Krotkine
- Mop module documentation
- "Mapping the MOP to Moose" by Stevan Little
- "The Art of the Metaobject Protocol" AMOP