Corinna: A modern and mature object system for Perl 5

With Corinna, the Perl programming language has had a more mature object system since version 5.38 without the previous weak point of missing keywords.

Save to Pocket listen Print view

(Image: Miriam Doerr, Martin Frommherz/Shutterstock.com)

16 min. read
By
  • Herbert Breunung
Contents
This article was originally published in German and has been automatically translated.

Version 5.38 provides new keywords for object orientation (OO for short) in the Perl language core. This is one of the most far-reaching enhancements since Perl 5.0 (1994) and therefore deserves a detailed analysis, which also reveals something about the long-term development of the language. But even for non-Perl programmers, the implementation of the new feature is quite insightful, as the Perl development team has solved a fundamental problem in a prudent and sustainable way.

OO definitions are rarely precise and often one definition contradicts the other, as Damian Conway –, who made a significant contribution to object-oriented programming in Perl –, repeatedly emphasizes. Alan Kay's approach is helpful, but the central challenge of most software projects remains: an almost suffocating complexity. Objects hide some of it behind the facade of their public interface, limiting the amount of information relevant to the programmer. Only for this purpose do objects also have a state (attributes).

In Perl 5.0, which introduced object orientation to the language, this hiding was only half-heartedly implemented. This is because Perl 5 objects are, apart from their reference type, normal data structures. Only the user's self-restriction prevents access to private attributes.

A second weakness was the lack of keywords. Experienced programmers can immediately see from a bless or use base which packages (namespaces) are classes and from my $self = shift; which sub functions as a method. However, the Perl development team could have made it easier for newcomers, instead of just repeatedly dismissing their objection that Perl has no real OO by pointing to the superior power of Perl OO.

It is true that any kind of OO can be built with the (meta-)system that is subsequently inserted into the language. However, it requires significantly more writing effort – even for everyday tasks that are solved more directly in other, otherwise much more loquacious languages. Together with the lack of signatures at the time, this circumstance left quite a few people with an archaic, clumsy impression.

Perl 6 was supposed to remedy all this, as 35 of the 361 initial Requests for Comments (RFC) already addressed partial problems. During a lengthy decision-making process, a powerful, very sophisticated OO system emerged from the RFC responses. However, Perl 6 developed into its language and was later renamed Raku. When it was clear that Perl 5 would not be replaced, Stevan Little created Moose. Moose was an object system that ported the Raku OO to Perl 5 as well as possible and extended it with the capabilities of the Common Lisp Object System (CLOS) and other languages. This was followed by a phase comparable to a gold rush, during which countless Moose plug-ins and several Moose alternatives were created. During this time, it became clear which functionalities were important and which notations were suitable.

When Curtis (Ovid) Poe set out to design the new OO under the project name Cor (later called Corinna). He was able to draw on the experience he had gained and also consult with the key minds behind this development including Damian Conway and Stevan Little. In the end, he chose a cautious and minimalist path, with a syntax that deliberately avoids confusion with Raku and Moose, and with semantics that fit seamlessly into Perl 5.

Corinna also does not introduce any new syntactic twists and consistently refrains from exceptions, such as assigning special meaning to certain symbols in identifiers (capital initial letters or ''). The semantics are purely declarative and the syntax adheres to simple, uniform rules – both influenced more by Raku than by Perl 5.

The most serious criticism of Corinna can be formulated as a question about the Perl Porter (p5p): Why wasn't this functionality possible ten years earlier, when it was foreseeable that Perl 6 would not replace Perl 5? The biggest technical weakness of Corinna, on the other hand, is the unavoidable fact that classes created via bless and Corinna classes cannot inherit from each other and also have separate base classes. For collaboration, developers only have the option of using OO by composition and using objects of the other type as attributes.

Corinna includes four new keywords: class, role, method and field, whereby role as well as around 70 percent of the specified functionality will not yet be delivered with version 5.38. Nevertheless, the following description covers all essential features and names what can already be used. Users of older Perl versions can integrate Corinna (as of 5.38) as a Compat::Class:Feature module.

Attributes are variables whose scope is declared with field, instead of my, our or state, which restricts their use to all methods of the current class or role. Logically, this is only possible within a class (class) or role (role). And as you would expect, their content is different for each object. During their initialization, they can be assigned a value using any expression or instruction. This is executed when the object is created (when the user calls Classes::Name->new(...)).

If one instruction is not enough, a whole block can also be inserted. This is evaluated as if there was a do before the block.

There can be any number of attributes between the variable name and the assignment. They are syntactically copied from Raku, but were already introduced with Perl 5.10 (2008). So far, however, they have received little attention, as they only help subroutines to achieve rarely used behavior. In Corinna, the uniform order applies to all keywords: keyword, name, attributes, code. The last two elements are optional.

The only field attribute implemented so far is :param. It specifies that the constructor accepts an argument with the same name, the content of which is automatically assigned to the field variable. If the names of the constructor argument and field variable differ, the :param attribute receives the other name as an argument.

field $timestamp :param(created) ||= time;

Class::Name->new( created => 0 );

If the field variable $timestamp was not assigned a value during initialization, the constructor would terminate with an error if it did not receive a value under the name created (and there was no <code> ||= time</code> in the example). In the example, it received a zero, which is not transferred to $timestamp, however, as the self-assigning operator ||= takes effect and gives priority to the logically true result of time.

The attributes :reader and :writer are planned, which trigger the creation of methods for read and write access (accessors). Here too, different names are permitted as attribute arguments, even if only to obtain a combined read and write method. This is because :writer without an argument has a set_ in front of the variable name. It should also be noted that field variables are not visible in derived classes. In the case of more exotic name conflicts, a field variable with :name(...) can have a different name when it is initialized than when it is used later in the methods. code]:reader[/code], :writer and :param then also refer to the name indicated as ....

:common marks class variables that allow data exchange between instances of a class and :handles(...) enables delegation.

field $datetime :handles(time:hms, now) = 'DateTime';

In this example, the $datetime field stores a DateTime object.

Calls such as $object->time are then automatically forwarded to the attribute of $object as $datetime->hms and $object->now becomes $datetime->now.