develooper Front page | perl.perl5.porters | Postings from June 2021

Re: This is not an RFC to bring modern OO into the Perl core

Thread Previous | Thread Next
From:
Chris Prather
Date:
June 20, 2021 04:52
Subject:
Re: This is not an RFC to bring modern OO into the Perl core
Message ID:
CAEFJ169v7iVg8LrHRFh55Kgc4RPKcs6M+Qh8jE-2E27K+WCt9A@mail.gmail.com
This is probably going to annoy some of you. If you want something
that is concise, relevant, and technical ... don't read this email.
It's any one of the three, but never all three together.

On Sat, Jun 19, 2021 at 9:41 PM mah.kitteh via perl5-porters
<perl5-porters@perl.org> wrote:
>
> On Saturday, June 19th, 2021 at 3:44 PM, Darren Duncan <darren@darrenduncan.net> wrote:
> >
> > Using "modern" to describe something is so over-used its lost all meaning.
>
> I don't think "post-modern" is helpful either. I much prefer the term "counter culutural".
>

Wikipedia says "A counterculture is a culture whose values and norms
of behavior differ substantially from those of mainstream society,
sometimes diametrically opposed to mainstream cultural mores."[1] so
obviously Corrinna cannot possibly be counter-cultural. Her norms of
behaivor do not differ substantially from those of either mainstream
Perl *nor* mainstream Object Oriented Programming. This was in fact
the original complaint about the term "modern".

Second, Corinna is patently not postmodern either. As has been
discussed before[2], postmodernity has three basic parts in its
definition, Corinna only matches one of them— “of, relating to, or
being an era after a modern one”. It is not a return to traditional
mediums and forms, it is also not an ironically self referential
commentary on the status quo. Neither is it entirely a radical
reappraisal of modern assumptions about culture, identity, history, or
language, in so far as I don't think anything about it is at this
point radical for anyone who's worked with an object oriented language
developed after the year 2006.

Corinna is a post-postmodern object system—though possibly not the
first one, Raku probably has a claim to that title. Corinna oscillates
between modernist positions regarding Object Oriented Design, and the
postmodern deconstructionist principles that were embraced
dramatically by Moose. Corinna is a metamodern[3] object system. "How
on earth is this relevant?" I imagine you asked *long* before you got
to this part of the email. It speaks to why the approach Brett is
advocating simply _won't_ work (beyond the fact that people have been
trying it for several years now).

> Rather than peeling the OOP "onion" back layer by layer, build it out from what exists now. Starting with what's needed to augment "bless", prototypes, and [overload.pm].

Without trying to sound offensive, this list kinda suggests you've not
really done any extensive thought about what an object system is and
should be. Most people don't and shouldn't ever need to. A list of
things that in my opinion would need enhancement:

* Classes: Perl just gives you packages with a special @ISA variable
for inheritance. Packages are just a bag of subroutines, they have no
idea of state.
* Attributes: `bless` associates a package with a data structure to
provide "attributes", except it doesn't actually provide attributes,
it just provides a place to store data and leaves you to figure out
what attributes are and what that means. This also means that all
instance data is public by default. While we pretend that it doesn't
because Larry told us not to play with shotguns, it hasn't stopped a
lot of people putting shotgun like things onto CPAN (or into Perl Best
Practices).
* Metamodel: The way you interrogate and manipulate a package is ....
not obvious. Package::Stash exists on CPAN simply to provide an API
for this manipulation because it's fraught with edge cases and weird
syntax.
* Methods: Perl's concept of a method is a subroutine called in a
funky way. Combined with the public nature of the data, this means you
can call any method on any object ... and the only thing that can
prevent this is the method itself. I've never seen anyone write enough
validation code at the beginning of their methods to deal with what is
actually possible to throw at a method.
* Class composition: _Design Patterns: Elements of Reusable
Object-Oriented Software_, published literally 4 days after Perl 5.000
says to prefer composition to inheritance. Perl's only solution to
reusable behavior is inheritance. Worse, Perl supports multiple
inheritance using a default algorithm that can cause weird non-obvious
bugs.
* Object Construction Protocol: Ensuring that all of the attributes
are initialized properly in the correct data structure during
construction is left entirely as a lemma for the programmer.
* Object Destruction Protocol: See above, but because Perl has
universal destruction where we can't even guarantee the order in which
things are destroyed.

The fact that Perl's built in object system just gives you a bag of
primitives and leaves you to build a robust object system for every
application you write is kinda the reason things like Moose exist.
Moose's choices to solve many of these problems is the reason Corinna
exists. Let's take Classes, attributes, and methods for example
(because this is the most obvious change in Corinna). Classes are
supposed to be a template for creating objects with initial
definitions of state and implementations of behavior. Perl's native
system only provides the second half of that.

````
package Player;
use 5.34.0;
use warnings;
use experimental 'signatures';

sub new($class, $data) {
    # a "character" property is required so we have to check here
   die "Must provide a character object" unless $data->{character};

    #  every player must have a name, but we can provide a default
    $data->{name} //= do { state $n = 1; 'Player' . $n++ };

    bless $data, $class;
}

# and we have to write our own accessor if we don't want people to
just use $self->{name}
sub name($self) { $self->{$name}  }

sub attack($self, $target) { print STDERR "$self->{name} attacks
$target->{name}"; ... }

1;
```
Moose provides a template for state but the way it provides it
encourages a proliferation of behavior;
```
package Player;
use 5.34;
use Moose;
use experimental 'signatures';

has name => ( is => 'ro', default => sub { state $n = 1; 'Player' . $n++ } );
has character => ( is => 'ro', required => 1 );

__PACKAGE__->meta->make_immutable;
```
This has templates for the state, but now our character object has a
public accessor. Moose can be told not to generate an accessor.
```
package Player;
use 5.34;
use Moose;
use experimental 'signatures';

has name => ( is => 'ro', default => sub { state $n = 1; 'Player' . $n++ } );
has character => ( is => 'bare', required => 1 );

sub attack($self, $target) { print STDERR $self->name() . " attacks "
. $target->name(); ... }
__PACKAGE__->meta->make_immutable;
```
But now the only way to access the character attribute is to look in
$self->{character} which poses a problem. Moose tries to hide the fact
that all objects are blessed hashes, they are, everyone knows they
are, but it's considered bad form to treat them as such because doing
that you throw away any advantages Moose can provide to attribute
access. We can fix that as well
```
package Player;
use 5.34;
use Moose;
use experimental 'signatures';

has name => ( is => 'ro', default => sub { state $n = 1; 'Player' . $n++ } );
has character => (
    is => 'bare',
    reader => '_character',
    required => 1
);

sub attack($self, $target) { print STDERR $self->name() . " attacks
$target->{name}"; ... }

__PACKAGE__->meta->make_immutable;
```
Now you have a public method that uses the Perl convention of
underscore prefix means it's private so pretend you don't see it. Oh
and to make all of this operate at a reasonable speed you have to
include __PACKAGE__->meta->make_immutable; at the end of every package
so that Moose spends a lot of compile time generating and eval-ing
strings into packages so that at runtime you get almost the same speed
as an equivalent native method.

From a design perspective this means that there is a lot of state and
behavior you either simply pretend aren't public or actively work
around Moose to encapsulate. Corinna's approach to this is to have
lexically scoped member variables that aren't accessible outside the
class.
```
use strict;
use warnings;
use feature 'class'; # bring in Corinna

class Player {
    has $name :new :reader = do { state $n = 1; 'Player' . $n++ };
    has $character :new;

    method attack($target) { say STDERR "$name attacks " .
$target->name(); ... }
}
```
This default is changed from public first, to private first. The only
subroutines in the class are the ones you explicitly ask for (:reader)
or define (method). Because Corinna is baked into core there is no
need to inject strings into packages at compile time to be parsed so
that everything runs at native speed. The augmentation kind of
requires the entire re-thinking of how classes are implemented in
Perl. Oh! And the pieces are hard to tease apart, because once you
start thinking about attributes as an essential part of a Class you
now have to have an Object Construction Protocol to make sure they're
initialized. An Object Construction Protocol is a part of a well
defined metamodel. If you want to encapsulate data into a lexical
scope you need methods that can access that lexical scope (and can't
access a different classes scope). So the augmentation starts hitting
several parts that Perl currently doesn't have ... just to get the
basic behavior.

You've slogged through all of this ... but yeah there's one more thing
I wanted to point out. You could reduce Corinna by getting rid of the
slot attributes like ':new' and ':reader'. All of the features we were
looking for with private attributes are still there, the code is just
more verbose.
```
use strict;
use warmings;
use feature 'simpler_class'; # a simpler Corinna

class Player {
   has $_name = do { state $n = 1; 'Player' . $n++ };
   has $_character;

   method new($character, $name=undef) {
      $_character = $character;
      $_name  =  $name if defined $name;
      return $self;
   }
   method name() { $_name }
   method attack($target) { say STDERR "$name attacks " . $target->name(); ... }
}
```

So where were we? Oh right! Corinna is a metamodern object system
because it rejects the postmodern concept of deconstructing the
existing object system (by rebuilding it with a robust framework) and
instead returns to the modernist design of extending the core language
with the attributes we found most appealing after gazing at the system
with our postmodernist mindset, but without the baggage that the
postmodernist ironic self-reflexive implementation required us to
have.

Corinna is metamodern.

Thank you for coming to my Ted Talk.

[1]: https://en.wikipedia.org/wiki/Counterculture
[2]: https://chris.prather.org/why-moose-is-post-modern.html
[3]: https://en.wikipedia.org/wiki/Metamodernism#Vermeulen_and_van_den_Akker

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About