
Metaobject Protocol for basic Perl 5
The idea of creating a Metaobject Protocol ( MOP ) for Perl 5 has been around for quite some time. One of the implementations is well known - Class :: MOP , which is used in Moose . But only a solution that can be compatible with the existing object model and will not be overloaded with unnecessary features and dependencies can get into the basic delivery of Perl 5. The other day, Stevan Little published the first test release on CPAN of a possible candidate for this vacant place - the mop module . The project went through a long evolution, the process was closely monitored by the community. Let’s take a look at what happened and what consequences this could have for Perl 5.
Metaobject 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 meta-object) with programmable properties (attributes, methods, etc.). As Stevan Little put it, explaining to his mom what Moose is doing:
When dealing with classes in basic Perl, we all came across the simplest implementation of MOP:
This crypto record during the program operation adds a method
If we turn to the classic book on MOP - “The Art of the Metaobject Protocol”, which described the implementation of the metaprotocol in CLOS , one of the main prerequisites for creating the protocol was the need to solve the problem with a variety of object system implementations for the Lisp language, which were incompatible with each other. The metaprotocol allowed to build a compatible, flexible and extensible object system that would satisfy all the requests of Lisp programmers.
The simple and dependency-free implementation of MOP in the basic Perl package can unambiguously solve the problem of choosing an object system to use when creating programs. This will end all religious wars in the confrontation of OOP frameworks and will make familiar MOP connection like
The implementation of MOP for basic Perl should be easy and fast enough, for this reason the same Moose will never be able to get there, but mop has every chance for this.
A syntactically beautiful and logical object system can inspire a lot of modules to be reworked, 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 maintaining it (single style , simplified static code analysis).
Naturally, this can attract young developers to Perl, because simplicity and conciseness are very important when mastering a language that claims to be the object-oriented.
As some people in the community have noted, it is likely that MOP will become an important part of Perl’s future and there will be a clear boundary in Perl’s history: before and after MOP comes into the kernel.
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 acquired experience 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 expecting for so long. But for a long time, the start of programs on Moose and a large dependency tree scared potential users. So two years ago, Stephen got excited about the idea of creating a compact MOP system that could be included in the basic Perl package. This is how the p5-mop project came about.but he was crushed by the weight of his own complexity, which somewhat disappointed Stephen and led him to an unexpected experiment - the Moe project , an implementation of the Perl 5 compiler in Scala .
Some time passed and Stephen decided that we still had to give p5-mop a second chance and created the p5-mop-redux project , which did not try to grasp the immensity and did not set out to drag all the possibilities of Moose, but only become the basis for an extensible object system in the core of Perl.
Stephen kept the community up to date on progress and published articles on the blog platform blogs.perl.org . On October 15, the first trial release of the module on CPAN was published. Now the module has two active developers: Stevan Littleand Jesse Luehrs , as well as several contributors.
To install mop, you can use cpanm:
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.
Consider a sample code using mop:
The class is defined by the keyword
After indicating twigil, descriptions of properties ( traits ) follow the keyword
This defines a class
If classes for inheritance are not specified, the class is inherited from the class by default
Attributes of an object can be set when creating an instance of a class:
Please note that we did not specify a method
To access the attribute value, a getter method is automatically created, for example:
The value will be displayed
Nevertheless, inside the methods we can freely change any attributes, for example, you can create a setter method
In this example, you can also clearly see how you can set the method signature, i.e. Describe the variables of the arguments passed to the method, and even set default values if the argument is omitted.
At the same time, attributes are in scope only for the Point class, i.e. we cannot directly operate with them in the heir class
This will throw a compilation error:
Roles allow you to flexibly compose classes with the necessary methods and attributes, avoiding multiple inheritance.
This role defines two methods
Now the class can be combined with this role using the keyword
A class can be composed of several roles, in which case the role names are listed with a comma.
Attribute properties are comma-separated after the is keyword. The following properties are currently supported:
When setting the default value, it should be remembered that in fact, after the sign is an executable construction, i.e. record:
Mean
Those. essentially, it is a function to create a value, not to assign a given expression.
When setting the default value, you can refer to the current instance of the object using the $ _ variable:
If it is required that when creating an object with the help
Accordingly, if the attribute value is
Checks of types, classes of attributes and methods are not implemented, so as not to overload the mop kernel with excessive complexity. However, such checks can easily be performed as an external function:
A working implementation of the function
A typical module file might look like this:
As can be seen from the example, if specified
A special method
The method
The object created by mop is not familiar to many blessed hash references. Instead, the 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:
The mop module is currently being actively tested by the community. The main recommendation is to take any module and try to rewrite it using mop. What mistakes and problems will you encounter? Write about this, it will greatly help in the further development of the project. For example, the Plack module was successfully ported , all 1152 tests of which were successfully passed.
Now it’s hard to say whether mop will be accepted as part of the core Perl distribution. If accepted, then starting with which version: 5.20, 5.22 or later? This is unknown, but the overall highly positive background surrounding the event is encouraging.
What is a MOP?
Metaobject 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 meta-object) with programmable 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 the simplest implementation of MOP:
no strict 'refs';
*{$foo . '::bar'} = \&baz;
This crypto record during the program operation adds a method
bar
for the class specified in the variable $foo
, which is the link (alias) to the function baz
. 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 tool for the developer to metaprogram classes without the need for semi-illegal hacking techniques. For example, like this:class Foo {
method bar {
baz
}
}
Why do I need a MOP in the basic Perl package?
If we turn to the classic book on MOP - “The Art of the Metaobject Protocol”, which described the implementation of the metaprotocol in CLOS , one of the main prerequisites for creating the protocol was the need to solve the problem with a variety of object system implementations for the Lisp language, which were incompatible with each other. The metaprotocol allowed to build a compatible, flexible and extensible object system that would satisfy all the requests of Lisp programmers.
The simple and dependency-free implementation of MOP in the basic Perl package can unambiguously solve the problem of choosing an object system to use when creating programs. This will end all religious wars in the confrontation of OOP frameworks and will make familiar MOP connection like
use strict
.The implementation of MOP for basic Perl should be easy and fast enough, for this reason the same Moose will never be able to get there, but mop has every chance for this.
A syntactically beautiful and logical object system can inspire a lot of modules to be reworked, 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 maintaining it (single style , simplified static code analysis).
Naturally, this can attract young developers to Perl, because simplicity and conciseness are very important when mastering a language that claims to be the object-oriented.
As some people in the community have noted, it is likely that MOP will become an important part of Perl’s future and there will be a clear boundary in Perl’s history: before and after MOP comes into the kernel.
Evolution mop
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 acquired experience 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 expecting for so long. But for a long time, the start of programs on Moose and a large dependency tree scared potential users. So two years ago, Stephen got excited about the idea of creating a compact MOP system that could be included in the basic Perl package. This is how the p5-mop project came about.but he was crushed by the weight of his own complexity, which somewhat disappointed Stephen and led him to an unexpected experiment - the Moe project , an implementation of the Perl 5 compiler in Scala .
Some time passed and Stephen decided that we still had to give p5-mop a second chance and created the p5-mop-redux project , which did not try to grasp the immensity and did not set out to drag all the possibilities of Moose, but only become the basis for an extensible object system in the core of Perl.
Stephen kept the community up to date on progress and published articles on the blog platform blogs.perl.org . On October 15, the first trial release of the module on CPAN was published. Now the module has two active developers: Stevan Littleand Jesse Luehrs , as well as several contributors.
Work with mop
Installation
To install mop, you can use cpanm:
$ cpanm --dev mop
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 a 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 keyword
class
. First, a class is declared Point
that defines the attributes using the keyword has
. Please note that attribute variables are set using twigil ( twigil or two-character sigil ) to distinguish from ordinary variables . This almost completely copies the syntax of Perl 6. Currently, only tweedle attributes $!
, which are private attributes, are supported . After indicating twigil, descriptions of properties ( traits ) follow the keyword
is
. For example, ro
means that the attribute is read-only. After the equal sign, the default attribute value is set. In classPoint
the only method clear
that resets attribute values is specified . It can be seen that the variable attributes $!x
, $!y
available in both methods of lexical variables in the scope of this class. Their values are closed within each instance of the class.class Point3D extends Point {
has $!z is ro = 0;
method clear {
$self->next::method;
$!z = 0
}
}
This defines a class
Point3D
that becomes the inheritor of the Point class using the keyword extends
. Thus, the resulting class takes over all the attributes and methods of the class Point
. In addition to them, an attribute is specified in the class $!z
. The method is also overridden clear
, which, as can be seen from the listing, calls the next (in the inheritance hierarchy) parent method clear
from the class Point
using next::method
. In addition, a variable is automatically defined inside each method $self
, which is a reference to the current object. If classes for inheritance are not specified, the class is inherited from the class by default
mop::object
. This is demonstrated in the following example:print Dumper mro::get_linear_isa(ref Point->new);
$VAR1 = [
'Point',
'mop::object'
];
Attributes of an object can be set when creating an instance of a class:
my $point = Point->new( x => 1, y => 1);
Please note that we did not specify a method
new
for the class Point
. This method is inherited from the class mop::object
. 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 will be displayed
1
. 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
Nevertheless, inside the methods we can freely change any attributes, for example, you can create a setter method
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 variables of the arguments passed to the method, and even set default values if the argument is omitted.
$point->set_x(5); # $!x теперь 5
$point->set_x; # $!x теперь 10
At the same time, attributes are in scope only for the Point class, i.e. we cannot directly operate with them in the heir class
class Point3D extends Point{
...
method set_x_broken {
$!x = 10;
}
}
This will throw a compilation error:
No such twigil variable $!x
Roles
Roles allow you to flexibly compose classes with the necessary methods and attributes, avoiding multiple inheritance.
role BlackJack {
method win;
method loose ($value) {
not $self->win($value)
}
}
This role defines two methods
win
and loose
. The method win
does not have a body, so in this case the method must be defined as part of the class that plays this role. In this regard, the role is similar to the concept of an interface present in other programming languages. Now the class can be combined with this role using the keyword
with
:class LunaPark with BlackJack {
method win ($value){
0
}
}
A class can be composed of several roles, in which case the role names are listed with a comma.
Attribute Properties and Values
has $!foo is rw, lazy = 0
Attribute properties are comma-separated after the is keyword. The following properties are currently supported:
ro
/rw
- read-only access / read and write accesslazy
- creating an attribute the first time you access itweak_ref
- the attribute is declared as a "weak" link
When setting the default value, it should be remembered that in fact, after the sign is an executable construction, i.e. record:
has $!foo = "value"
Mean
has $!foo = sub { "value" }
Those. essentially, it is a function to create a value, not to assign a given expression.
When setting the default value, you can refer to the current instance of the object using the $ _ variable:
has $!foo = $_->set_foo()
If it is required that when creating an object with the help
new
a certain attribute must be set, in the default value for it you can specify the following code:has $!foo = die '$!foo is required';
Accordingly, if the attribute value is
foo
not set when creating the object, an exception will occur. Checks of types, classes of attributes and methods are not implemented, so as not to overload the mop kernel with excessive complexity. However, such checks can easily be performed as an external function:
sub type {
# Функция выполняющая проверку типов
...
}
class foo {
has $!bar is rw, type('Int');
method baz ($a, $b) is type('Int', 'Int') {
...
}
}
A working implementation of the function
type
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 can be seen from the example, if specified
package
, then the class names receive the corresponding prefix. In addition, the class gets access to the scope of the module. This means that functions and variables defined in the scope of the module are also available for use inside classes. However, such functions do not become class methods. No more namespace pollution with exported features! A special method
BUILD
can be used if any initialization is required when creating the object. This is convenient and allows you to not override the method new
. The method
DEMOLISH
is called when the object is destroyed, i.e. is a destructor.The internal structure of the object in mop
The object created by mop is not familiar to many blessed hash references. Instead, the 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 dumpprint 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
- unique identifier of the objectprint mop::id(Point3D->new) 10804592
mop::meta
- meta-information about the object, for a detailed introspection of objectsprint Dumper mop::dump_object( mop::meta( Point3D->new ) ) $VAR1 = { '$!name' => 'Point3D', ... # очень длинная простыня с выводом всех свойств объекта ... }
mop::is_mop_object
- logical function, returns true if the objectmop::object
print mop::is_mop_object( Point3D->new ) 1
Practical use
The mop module is currently being actively tested by the community. The main recommendation is to take any module and try to rewrite it using mop. What mistakes and problems will you encounter? Write about this, it will greatly help in the further development of the project. For example, the Plack module was successfully ported , all 1152 tests of which were successfully passed.
Now it’s hard to say whether mop will be accepted as part of the core Perl distribution. If accepted, then starting with which version: 5.20, 5.22 or later? This is unknown, but the overall highly positive background surrounding the event is encouraging.
Sources
- Perl 5 MOP by Stevan Little
- "Why Perl 5 Needs a 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