Skip to content

Instantly share code, notes, and snippets.

@perigrin
Last active April 7, 2020 01:19
Show Gist options
  • Save perigrin/31cb41845219ff68f65abcc3126deaeb to your computer and use it in GitHub Desktop.
Save perigrin/31cb41845219ff68f65abcc3126deaeb to your computer and use it in GitHub Desktop.
layout title author categories tags date image
post
Metaobjects in the Cor
Chris Prather
oo, perl, mop
Fri Apr 3 13:06:07 CDT 2020

Back in Oct 16, 2019 Ovid published his proposal for a core object system for Perl code-named Cor. I've been thinking about how to respond ever since, but life has gotten in the way time and again.

We're talking away

The Proposal that Ovid makes is good on it's surface. It's very good in a lot of ways, and based on feedback from various parts of the community it's gotten better. The places I have disagreements with the syntax all fall into "work in progress" categories. Not that my opinion really matters.

An example of the syntax (as it currently stands):

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

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

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

The biggest change Moose brought to Object Oriented Programming in Perl was to make Attributes a first class member of the object system. Before Moose, Attribute data was a side effect of the object representation and whatever accessors the developer happened to decide to provide (if any). With Moose the idea of what state an object contained stepped to the foreground.

Cor continues this, but takes it a step further. One of the complaints about moose is that it exposes too much automatically in the objects API. Moose's has function exports too much by default. Cor picks better defaults for accessor and constructor behavior and learn the lessons we've learned from the [M-star]1 collection of object systems on CPAN.

But Cor is missing the second biggest advancement Moose brought to Perl, The Metaobject Protocol.

I don’t know what I’m to say I’ll say it anyway

From Wikipedia:

A metaobject protocol (MOP) provides the vocabulary (protocol) to access
and manipulate the structure and behaviour of systems of objects.

Moose has a complex set of objects and classes that allow you to override and extend nearly every piece of it's infrastructure. So many objects and classes that people started trying to ditch them, leading to the creation of Moo2. The Cor propoasal so far has left the MOP entirely undefined.

Without a Meta-Object Protocol, any object system in Perl's core won't be revolutionary, and will most likely be doomed to failure.

Today is another day to find you

Paul "LeoNerd" Evans has started a proof of concept project influenced by the Cor proposal, Object::PAD. Technically it's a proof of concept for an Object system where the attribute slots are lexical variables, but he's leveraging the syntax work that Ovid has done with Cor to proceed.

One of the bugs that he ran into turns out to be a bug in standard Perl too.

# DO NOT USE THIS BUGGY CODE
package Parent {

    sub new ($args) {
        my $self = bless $args, __PACKAGE__;
        $self->init()
        return $self;
    }

    sub init ($self) { }
}

package Child {
    use parent 'Parent';

    sub new($args) {
        my $self = shift->SUPER::new($args);
        $self->{foo} //= "bar";
    }

    sub init ($self) {
        warn "the value of foo is: $self->{foo}";
    }
}

Child->new()

In production this subtle bug (did you spot it?) will trigger an undefined warning when it tries to run Child::init before populating $self->{foo}. Or if you have warnings as fatal (because say you're trying to access a non-existant lexical slot) it'll blow up.

In Java for example, during object construction any method calls (like $self->init()) are dispatched on the parent class until the child class has finished being constucted.

Shying away

Perl has had Object Oriented Programming for over 20 years with an ad-hoc MOP.3 Object construction is simple: bless $data, $class. Classes are just Packages, so if you want to manipulate a class dynamically just manipulate the package's symbol table.

# DEMO CODE DO NOT USE
package Student {
    sub new ($args) { 
        die unless exists $args->{id};
        return bless $args, __PACKAGE__ 
    }

    # create some accessors
    for my $method (qw(name id grade teacher)) {
        no strict "refs";
        *Student::$method = sub ($self, $value) {
            if (defined $value) {
                $self->{$method} = $value;
            }
            $self->{$method}
        }
    }
}

This has the advantage of being a very flexible system.[^4] But it relegates attributes to be almost a side-effect of objects having state, and requires a lot of manual work on the part of the project developer to ensure that the object system is correct and consistent. Separating those concerns out is the process of defining a Meta-Object Protocol.

I'll be coming for your love. OK?

Stevan Little has givena fewtalks about building MOPs for Perl. (That list isn't exhaustive, it's just the top 3 I found on YouTube). His most recent works are UNIVERSAL::Object and MOP.

UNIVERSAL::Object defines a very basic MOP for objects in Perl. It adds attributes as a first class concept, not as a side-effect of object state. Because it defines an initialization order (via BUILD methods) in the MOP you can avoid the $self->init() bug we saw earlier.

# THIS CODE IS SAFE BUT USELESS
package Parent {
    use UNIVERSAL::Object;
}

package Child {
    use parent 'Parent';

    our %HAS = (
       %Parent::HAS,
       foo = sub { "bar" }, 
    );
    
    sub BUILD ($self) {
        warn "the value of foo is: $self->{foo}";
    }
}

Child->new()

We give up the flexiblity of writing our own constructor, but we get back consistency and a defined order of operations during object construction.

MOP expands on this to include primarily introspection and Role composition.

Fun and Games: Examples of how things could work

Midpoint: We have a unified system (false victory)

Bad Guys Close In: Traditonal Perl doesn't have this

Perl has never had explicit layers like this. Even XS is really just a series of macros that "safely" expose parts of the Perl core.

##All Is Lost: MOPs are a performance and maintenance overhead

Adding a MOP means adding a lot to the Cor proposal. Not only do all the syntax changes have to be incorporated, but lots of new protocols have to be defined, vetted, and then later maintained.

Dark Night of the Soul: backcompat ???

Break into Three: ???

Finale: ???

Final Image: ???


The banner image is Longing by CaseyWest, on Flickr.

[^4] Give or take the bugs outlined in the comments in [Package::Stash::PP][package-stash]. Or the fact that Package::Stash::XS is the preferred version for correctness.

Footnotes

  1. Moose, Moo, Mouse, Mojo::Object etc. All attempt to use similar semantics with various levels of support. Hence M*.

  2. Moo has just enough meta to make things work and if you want more than that it goes and loads Moose anyway.

  3. All object systems have a Metaobject Protocol. Some just have an explicit MOP while others have an implicit system. Perl's ad-hock system of bless(), and Package stashes forms a MOP, just not a very well defined one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment