Bringing Modern OO To Perl
Tags: perl, oop, programming
Published: 2021-03-01 11:18:41

Years ago, when I first stumbled on Perl, I had been programming in many languages, including C, assembler, COBOL, BASIC, and Java. I had been working at an insurance company and replicated 80 lines of COBOL in only ten lines of Perl and jumped ship. Since then I've written books about the language, keynoted several conferences, and sit on the Perl Foundation Board of Directors.

Fast forward to today and I'm still working with Perl, though often as a consultant, and more on a management level, overseeing projects. But I like to keep my hand in and unfortunately, Perl's object-oriented programming (OOP) syntax is wanting. Our company, [All Around the World](https://allaroundtheworld.fr/) is routinely contacted by clients wanting us to build new systems in Perl or to rescue legacy implementations and I brace myself for whatever variant of "OO programming" they happen to use. You see, Perl suffers from [The Lisp Curse](http://winestockwebdesign.com/Essays/Lisp_Curse.html). It's just so _easy_ to write your own OO system. I've done it myself. Heck, I even created (as a joke) one that used an inverted inheritance MRO similar to the [BETA programming language](https://beta.cs.au.dk/).

Today, Perl's core OOP system relies on something called `bless` and is conceptually very similar to how Python implements OOP. But most developers today hit [the CPAN](https://metacpan.org/) and download one of the myriad OOP systems and lament that their personal favorite isn't part of the Perl core.

I decided to fix that. But what _should_ be in the Perl core? While there are tons of different OOP systems out there, it's safe to say that [Moose](https://metacpan.org/pod/Moose) and [Moo](https://metacpan.org/pod/Moo) are the most popular choices. For reasons I won't belabor here, the Perl 5 Porters—the developers who maintain the language—have not accepted Moo/se into the core. But should they?

[Stevan Little](https://metacpan.org/author/STEVAN), the author of Moose, has been clear that Moose shouldn't be in the core and he was working on [Moxie](https://metacpan.org/pod/Moxie), something he felt was a much better choice. In fact, it _is_ a much better choice, but only if you accept the current limitations of Perl's syntax. I didn't want to, so [Corinna](https://github.com/Ovid/Cor/wiki) was born. This is nothing short of a radical attempt to overhaul Perl OOP in a way that is both backwards-compatible, but also allows the overall language to grow. And to say that the syntax is, um, nicer, is a massive understatement.

This, for example, is a simple LRU cache:

```perl
class Cache::LRU {
  use Hash::Ordered;

  has $max_size :new  :reader = 20;
  has $cache    :handles(get) = Hash::Ordered->new;
  has $created  :reader       = time;

  CONSTRUCT(%args) {
    if ( exists $args{max_size} && $args{max_size} < 1 ) {
      croak("max_size argument must not be less than 1");
    }
    return %args;
  }

  method set ( $key, $value ) {
    if ( $cache->exists($key) ) {
      $cache->delete($key);
    }
    elsif ( $cache->keys > $max_size ) {
      $cache->shift;
    }
    $cache->set( $key, $value );  # new values in front
  }
}
```

While we still have sigils (the punctuation characters) for our variables, if you're familiar at all with Perl, you'll probably note that this is much easier to read than a lot of the legacy Perl that's out there.

Corinna is intended to be standard class-based OOP, but with much of the code reuse provided by "roles." These are [Smalltalk-style traits](http://scg.unibe.ch/scgbib?display=abstract&query=nathanael+traits+composable+units+ecoop), but with an emphasis on [the formal model](http://scg.unibe.ch/archive/papers/Scha02cTraitsModel.pdf) (pdf) to ensure that ordering and grouping do not affect the behavior, unlike with inheritance or mixins.

The work for this project is focused on building an MVP and iterating on top of that. Thus, many features are omitted for now, but that will give us a chance to see what features we actually need instead of what features are being requested.

[Paul Evans](https://metacpan.org/author/PEVANS) has been writing [Object::Pad](https://metacpan.org/pod/Object::Pad), a testbed for many of the ideas we're implementing and it's been going well. There is broad community support, though there's been some pushback, too. I've also been working with some of the core Perl developers to get buy-in.

If we keep going the way we currently are, I'd cautiously say that we will have a proposal for the Perl core that will have a good chance of being accepted as an experimental feature. Further, Paul intends to release a CPAN module that will allow older versions of Perl to take advantage of the new features.

We don't have a timeframe, but we do have a vision. And so far, it's looking pretty exciting.

* [Core Perl OOP MVP features](https://github.com/Ovid/Cor/wiki/Corinna-Overview)
* [The Rationale](https://github.com/Ovid/Cor/wiki/Rationale)
* [A subset of the grammar](https://github.com/Ovid/Cor/wiki/Cor-Grammar)

Here's a presentation I gave at the 2021 FOSDEM.

{% youtube kSZO-aJg4dM %}

As a final note, look at the `Cache::LRU` code above. Here it is written in core Perl. I know which one I would rather write.

```perl
package Cache::LRU;
use strict;
use warnings;
use Carp;
use Hash::Ordered;

sub new {
    my ( $class, %arg_for ) = @_;
    my $cache    = Hash::Ordered->new;
    my $max_size = $arg_for{max_size} || 20;
    unless ( $max_size + 0 > 1 ) {
        croak("Invalid max_size argument: $max_size");
    }
    bless {
        cache    => $cache,
        max_size => $max_size,
        created  => time,
    } => $class;
}

sub created  { return $_[0]->{created} }
sub max_size { return $_[0]->{max_size} }
sub _cache   { return $_[0]->{cache} }

sub set {
    my ( $self, $key, $value ) = @_;
    if ( $self->_cache->exists($key) ) {
        $self->_cache->delete($key);
    }
    elsif ( $self->_cache->keys >= $self->max_size ) {
        $self->_cache->shift;
    }
    $self->_cache->set( $key, $value );
}

sub get {
    my ( $self, $key ) = @_;
    $self->_cache->get($key);
}

1;
```