Designing an OOP System
Tags: oop, perl
Published: 2021-03-03 10:49:17

Language design is all fun and games until you cut yourself on the edge cases. Case in point would be [Corinna](https://dev.to/ovid/bringing-modern-oo-to-perl-51ak), an attempt to bring modern OOP (object-oriented programming) to the Perl programming language. It's been fun, but painful. And the core design of Corinna has been revisited multiple times to sand down the edges. It's been a journey.

# The Background

Perl has a multiverse of broken, half-implemented, slow, inconsistent OOP implementations available on the CPAN. If you've ever read [The Lisp Curse](http://winestockwebdesign.com/Essays/Lisp_Curse.html) you understand why this is.

Newcomers to Perl are often perplexed by strange talk about blessing references, but today, they're just told to download Moose from the CPAN. Or Moo. Or the long tail of many other OOP implementations.

This situation has been unsatisfying for many experienced Perl developers. [Stevan Little worked for a long time trying to define a solid OOP system for the Perl core](https://www.youtube.com/watch?v=lnleCcKqcv4), but stopped. He did some great work, so I picked up the baton. And then the Perl Pumpking (the person who formerly oversaw Perl development), Sawyer X, told me to stop worrying about making something work and focus on design. If the design was good enough, they'd get someone to make it work.

Hence, Corinna.

# Corinna

So what is Corinna? In short, it's an attempt to rewrite a part of the Perl language to allow a clean, class-based OO system that is comfortable for those preferring the dominant Moo/se model. It offers subtle affordances for _doing the right thing_, but still gives you the freedom to do something others would consider wrong.

For example, if you use [Moo](https://metacpan.org/pod/Moo), how would you declare a `Person` object with just a `name` and an optional `title` attribute (think of attributes as "instance data")? (For the sake of brevity, we're going to ignore discussions about type constraints)

```perl
package Person {
    use Moo;

    has 'name' => (
        is       => 'ro',
        required => 1,
    );

    has 'title' => (
        is => 'ro',
    );
}
```

I can then call `my $person = Person->new( name => 'Ovid' );` and later, `$person->name` returns "Ovid", as expected.

If you're familiar with OO design, you know that just sharing all of your instance data by default is not a good idea. But in Moo (and Moose), it's difficult to write a useful attribute without giving it a public reader. Imagine you have a business rule which states that you must always refer to a customer by title+name if they have a title. You might want the `name` attribute reader to go away and be replaced by your own. To do that and keep your code working, you would add an `init_arg` key to the `name` declaration and rename `name` to `_name`:

```perl
package Person {
    use Moo;

    # init_arg is the name used in the constructor
    has '_name' => (
        is       => 'ro',
        required => 1,
        init_arg => 'name',
    );

    has 'title' => (
        is => 'ro',
    );

    sub name {
        my $self = shift;
        return defined $self->title 
            ? $self->title . ' ' . $self->_name
            : $self->_name;
    }
}
```

That's looking a touch awkward and maybe you don't want them to be able to read the `title` directly and you certainly don't want people calling `$person->_name`. And if a subclass happens to override the "private" `_name` method? Your code breaks. So here's how it looks in Corinna:

```perl
class Person {
    has $name  :new;
    has $title :new = undef; # optional

    method name () {
        return defined $title ? "$title $name" : $name;
    }
}
```

In the above, you can pass `name` and an optional `title` to the constructor and they're perfectly encapsulated. You don't wind up with extra `$person->title` or `$person->_name` methods littering your namespace, and it's easier to read, to boot.

What if I _want_ people to be able to read the title? Just add a `:reader`:

```perl
has $title :new :reader = undef;
```

Note that you can't actually _set_ the title after object construction. This is one of the ways that Corinna tries to subtly reinforce not only encapsulation, but immutability. However, the design encourages immutability; it doesn't require it. If you want mutability, it's trivial. Just add a `:writer` and you can do `$person->set_title($new_title)`:

```perl
has $title :new :reader :writer = undef;
```

# Builders

And then we have little things called "builders" in Moo/se terminology. Sometimes you want to call code to construct your attribute value:

```perl
package Thing {
    use Moo;

    has 'attr' => (
        is       => 'ro',
        required => 1,
    );

    has 'reverse_attr' => (
        is      => 'ro',
        builder => '_build_reverse_attr',
    );

    sub _build_reverse_attr {
        my ($self) = shift;
        return scalar reverse $self->attr;
    }
}

say Thing->new( attr => 'pupils' )->reverse_attr;
```

The above prints `slipup` as you might expect. But what if `attr` was named `source`?

```perl
package Thing {
    use Moo;

    has 'source' => (
        is       => 'ro',
        required => 1,
    );

    has 'reverse_attr' => (
        is      => 'ro',
        builder => '_build_reverse_attr',
    );

    sub _build_reverse_attr {
        my ($self) = shift;
        return scalar reverse $self->source;
    }
}

say Thing->new( source => 'pupils' )->reverse_attr;
```

Now you get a warning:

> `Use of uninitialized value in reverse at person.pl line 20.`

That's because attributes are built in alphabetical order for Moo. This leads to people regularly declaring attributes as `lazy` to ensure the builder isn't called until (and if) the attribute is called:

```perl
    has 'reverse_attr' => (
        is      => 'ro',
        builder => '_build_reverse_attr',
        lazy    => 1,
    );
```

Before we explore further, let's look at the above in Corinna. Attributes are (generally) assigned values in the order they're declared (the exception being that, once you hit a `:new` attribute, they're all gathered and assigned as a group). Here's equivalent code in Corinna:

```perl
class Thing {
    has $source       :reader :new;
    has $reverse_attr :reader = scalar reverse $source;
}
```

No fancy tricks required. 

That fixes this situation, but what's that `_build_reverse_attr` doing there in the Moo code in first place?

Unfortunately it creates a new method in your namespace. Thus, any subclass can now override this behavior and you have little control over it. [I've discussed this more at length elsewhere](https://ovid.github.io/articles/the-problem-with-builder.html). If you have any unusual construction requirements for your attributes in Moo/se, the natural _default_ behavior is to allow subclasses to violate encapsulation! I ran `git grep -c '\<builder\s\+=>' lib` over a few local codebases and was disturbed by how common this behavior was. Even a brief perusal of the code showed that for most of these classes, I did not want to expose this behavior.

But let's say you really, really want to let subclasses override your instance data. It's still possible. You still create a "builder" method, but you have to call it explicitly in the `ADJUST` phase (called after the object is constructed, but immediately before it's returned from the constructor).

```perl
class Thing {
    has $source       :reader :new;
    has $reverse_attr :reader;

    ADJUST (%args) {
        $reverse_attr = $self->_build_reverse_attr;
    }

    method _build_reverse_attr () {
        return scalar reverse $source;
    }
}

class Child isa Thing {
    method _build_reverse_attr () {
        return uc scalar reverse $self->source;
    }
}
```

So Corinna doesn't take anything away. It just makes the _natural_ use of the class a touch closer to OOP best practices, but still allows you to "be naughty" if you really insist upon it. This fits well with the spirit of Perl.

It's also interesting to consider this:

```perl
my $object = Some::Class->new;
```

What happens when you call `->new`? Well, it creates the object. In commonly recommended "best practice" for OOP, whatever object that is returned by the constructor should be _complete_ and ready for use. You usually don't want to call `new` and then `init` or other methods to make the object usable.

However, up until the point that the constructor returns the instance, there is no guarantee that its usable. Why does this matter? Because between the time you call the constructor and the time it returns an instance, the class is doing a bunch of stuff to set itself up. And if you're calling builder methods—which might in turn call other methods—during this time, you're not guaranteed that the class is ready for use. This is why in our `Moo` code we had to declare `reverse_attr` as `lazy` to ensure it wouldn't be called before the instance was ready.

We managed to skip that in Corinna because the attributes are assigned their values in declaration order, so by the time we got to `$reverse_attr`, we knew that `$source` should have a value (again, punting on types).

So those are just a few of the design considerations which have gone into Corinna. The resulting Perl code tends to be shorter and more declarative than Moo/se, and it tends to push you towards better encapsulation and immutability, though it doesn't require it. 

If there are particular topics about Corinna you would like to hear about, please let me know.