⬆️ ⬇️

Metaobject Protocol for Basic Perl 5

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); # $!x  5 $point->set_x; # $!x  10 


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:







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 { #     ... } class foo { has $!bar is rw, type('Int'); method baz ($a, $b) is type('Int', 'Int') { ... } } 


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:







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



  1. "Perl 5 MOP" by Stevan Little
  2. "Why Perl 5 Needs and Metaobject Protocol" by chromatic
  3. "P5-mop, a gentle introduction" by Damien "dams" Krotkine
  4. Mop module documentation
  5. "Mapping the MOP to Moose" by Stevan Little
  6. "The Art of the Metaobject Protocol" AMOP

Source: https://habr.com/ru/post/198274/



All Articles