DEV Community

Cover image for Bringing Modern OO To Perl
Ovid
Ovid

Posted on

Bringing Modern OO To Perl

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 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. 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.

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 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 and 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, the author of Moose, has been clear that Moose shouldn't be in the core and he was working on 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 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:

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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, but with an emphasis on the formal model (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 has been writing 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.

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

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.

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;
Enter fullscreen mode Exit fullscreen mode

Latest comments (15)

Collapse
 
philiprbrenan profile image
philip r brenan

Please provide some test cases so that we can improve your code.

Collapse
 
mmcclenn profile image
Michael McClennen • Edited

I like this! I think you are on the right track. And I agree 100% that the proper goal is to produce a better OO language than any available today. Perl has been falling behind the curve for too long now, and if we are going to revitalize the community we need to be jumping ahead rather than catching up.

The one part of the specification that worries me so far is that slot variables defined after the last :new slot variable in a class will be undefined when CONSTRUCT is called. It seems to me that "the last :new slot variable" is going to be a difficult thing to keep track of in large class definitions, where developers may be adding and removing :new from various declaration lines as development proceeds.

I suggest instead an additional attribute :after, such that slot variables with this attribute will not be defined until after CONSTRUCT is called. All other slot variables will be defined before CONSTRUCT is called, in the order defined. Specifying this explicitly rather than positionally will, I think, avoid a lot of confusion.

Collapse
 
ovid profile image
Ovid

Since this was posted, we've had a lot of discussion around CONSTRUCT and have since realized that as a phaser, it actually doesn't work since it needs to return a value. So we dropped it entirely. Now, we just use alternate constructors if we need to munge the arguments, or do validation in the ADJUST phaser.

Collapse
 
mmcclenn profile image
Michael McClennen

Good, the implementation of CONSTRUCT as proposted on the current Wiki page bothered me a great deal. I agree that dropping it was a good choice. Can you point me to where these discussions are going on? I'd love to follow along and give my input where warranted.

Thread Thread
 
ovid profile image
Ovid

Largely they're on irc.perl.org on the #cor channel. Mostly it's pretty quiet right now because we've nailed down much of the behavior and the push to get the next version of Perl out the door is taking priority.

Collapse
 
moopet profile image
Ben Sinclair

I looked at some Perl I wrote the other day, from about 15 years ago or thereabouts.
I have literally no idea how any of it works, and it's kind of turned into that thing.

Collapse
 
sigzero profile image
sigzero

I am super excited to see this come to fruition. There seems to be very careful thought going into what "Corinna" should be in the core.

Collapse
 
mhd profile image
Michael Dingler • Edited

As with all syntax issues, it's a pretty silly pet peeve, but there's one thing I reallly like better in the core version: There's one indentation level less for most of the file. Most of the time you don't have multiple classes per file, so it's usually quite obvious that you're within class scope and I don't need another 4 char margin (or preferably 8, if I'm not beaten over the the head with PBP by co-workers).

Collapse
 
cliff profile image
Cliff Stanford

Actually, I'm probably going to get slated for this but I prefer the core version. I'd have written it more concisely but, IMO, it's easier to read and easier to write.

Collapse
 
ovid profile image
Ovid

You're not going to get slated at all! Everyone has their preferences and we won't be removing bless from the core. So if you prefer to write your OO code that way, go right ahead. We won't break it.

Collapse
 
ovid profile image
Ovid

I know what you mean and people have pointed this out a few times. The reason for this is when I mention that this is designed to be "backwards-compatible, but also allows the overall language to grow."

By having that extra level of indentation and putting the class definition in a block, we guarantee that, unless you're doing something really, really strange, it's backwards-compatible. That's because this code could never have compiled on older versions of Perl. The introduction of that block structure gives us the freedom to do anything in that block we feel is necessary, but companies can rest assured that they can still upgrade without our changing anything that currently exists.

Collapse
 
jplindstrom profile image
Johan Lindstrom • Edited

I don't understand the argument. How is "it can't compile" backwards compatible? It's new syntax that should blow up in earlier versions. And it does.

Aside from that, a class declaration with / without a block seems to blow up the same way:

class Cache::LRU;            # to end of file
class Cache::LRU { ... };    # to end of block
Enter fullscreen mode Exit fullscreen mode

So what is the argument?

Thread Thread
 
ovid profile image
Ovid

This is something that was pointed out to me (by Sawyer? can't recall) a while ago as an unintended benefit. In short, if I wanted to repurpose syntax such as my Dog $spot, I'd be stepping on existing syntax. However, by creating a new syntax with an unambiguous scope and is guaranteed not to run on older versions of Perl (short of something really bizarre going on), we have a brand new syntax which is guaranteed not to clash with existing usage.

Further, because its scope is well-defined, we can play around with new syntax in that scope. Just adding a has function or a method keyword to the language could break all sorts of existing code that is already trying to do something like that. But by doing it in a new scope, we're safe.

Collapse
 
mhd profile image
Michael Dingler

As I've mentioned this is only a slight pet peeve and doesn't really impact me all that that much one way or another. Raku using the nested blocks alone would be a good reason for using that.

But I do feel a bit daft nonetheless: A top-level method wouldn't compile either, just like one nested within a class. On the other hand, once you bring in stuff like Moops, Dios or just Function::Parameters, everything is possible.
Having everything isolated in a block is a good visual boundary, definitely agree on that, but "could never have compiled" is a bit hard to parse for me on a Monday 😉

Collapse
 
systemz profile image
Zahir Lalani

Its been a long wait. Really looking forward to this