Cor - Background core Perl OO
So far, the work on Cor is going well. Here's the timeline.
When I was at the last Perl conference in Riga, I approached Sawyer X, the current Perl pumpking, about my idea for bringing a more modern OO into the Perl core. Much of my work is largely based on the work of Stevan Little, who's also been working on this, but focusing on what we might think of as "proof of concept" OO to flesh out the bugs that may be lurking there.
I had a partially working implementation, complete with test suite. My intent was that I would finalize it, publish it, and P5P would have a working test suite to validate their results. I was following the Pugs model of writing up a working implementation to flesh out the bugs.
In this case, it was a mistake. Sawyer argued, and I agreed, that with P5P, there was a group of brilliant developers that could implement Cor, but I had to actually have a spec to implement. By focusing my time on the spec instead of the implementation, I could work on creating a beautiful OO implementation that felt "perlish" without getting constantly bogged down in implementation details. We can (and will) revisit design when the implementation details show the inevitable problems.
So in coordination with Sawyer and Stevan, I wrote a Cor spec. They shot large parts of it down pretty quickly, so eventually the current Cor spec arose.
It has some issues. Largely, we have problems with data. The data we use with objects has several different aspects we need to worry about.
- Internal state
- State passed to the constructor
- Public access to said state
- Internal access to said state
Moose/Moo and friends, while being the overwhelmingly dominant choise of Modern Perl developers working on OO, suffer from conflating those various needs and trying to stuff most of that into a single has
declaration. That leads to the following, perfectly non-sensical, Moose code (which nonetheless runs just fine):
#!/usr/bin/env perl
use v5.24.0;
use warnings;
use DDP;
package Foo {
use Moose;
has attribute => (
is => 'ro',
isa => 'HashRef',
writer => 'set_foo',
required => 1,
builder => '_build_attribute',
);
sub _build_attribute {
return { this => 'value' };
}
sub this {
my $self = shift;
return $self->{attribute}{this};
}
}
my $foo = Foo->new;
say $foo->this;
my $attribute = $foo->attribute;
$attribute->{this} = 'whoops!';
say $foo->this;
my %encapsulation_violation = ( this => 'that' );
$foo->set_foo(\%encapsulation_violation);
say $foo->this;
That prints out:
value
whoops!
that
But what does that attribute even mean?
First, it's read-only, but has a writer.
Second, even without the writer, it returns a hashref which we can change and cause action at a distance by changing the state of the object.
Third, why is the attribute "required" (which implies it needs to be passed to the constructor) and yet still has a builder?
These are the sort of threads we need to disentangle. At first, we thought simply that we'd allow easy slot (data) declaration and since it was trivial for developers to quickly write accessors, it would offer an affordance for creating immutable objects which expose minimal state. And then I wrote this example of how it could be done with a mutable accessor:
method x ($x = undef) {defined $x ? self->x($x) : self->x}
See the bug? How can you set x
to undef
, if you need to?
This is better:
method x ($x = undef) {@_ == 1 ? self->x($x) : self->x}
But now we're re-exposing @_
, something we'd like to avoid, and we're making it easier for developers to write buggy code. Note that the example method I quickly wrote is something I regularly warn developers not to do! I'm very comfortable with Perl OO and I wrote a very common bug that we don't need to have. In fact, even my local sample implementation doesn't have this bug because I know it's a common, silly bug!
Sawyer has recently gotten back from a P5P event and Stevan is on vacation, so there will be a delay, but that's a good thing. We need time to sort through this and many other issues which have been raised.
Patience! 😃
Leave a comment