Skip to content

Instantly share code, notes, and snippets.

@Ovid Ovid/cor.md
Last active Nov 8, 2019

Embed
What would you like to do?
Cor—A minimal object system for the Perl core

NAME

Cor — A minimal OO proposal for the Perl core

VERSION

This is version 0.10 of this document.

AUTHOR

Curtis "Ovid" Poe

CAVEAT!

Nothing in the following proposal is set in stone.

DESCRIPTION

It has been repeatedly proposed that we have OO in the Perl 5 core. I support this notion. However, there's been much disagreement over what that OO should look like. I propose a simple OO syntax that would nonetheless be modern, but still "feel like Perl 5." Here's a small taste (will be shown again later in the document):

class Cache::LRU {
    use Hash::Ordered;
    
    has cache    => ( default => method { Hash::Ordered->new } );
    has max_size => ( default => method { 20 } );

    method set ( $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 );
    }

    method get ($key) { self->cache->get($key) }
}

To distinguish this OO system from the (too) many others, such as Moose, Moo, Dios, Class::InsideOut, Mu, Spiffy, Class::Simple, Rubyish::Class, Class::Easy, Class::Tiny, Class::Std, and so on, I'm going to call this one "Cor" (short for "Corinna", a possibly fictional woman that the poet Ovid would write poems to). Using the name "Cor" is only for disambiguation. I hope Cor would become core and thus not need a name.

This document should be considered a "rough draft". While I (Ovid) am the initial author, this document has been heavily updated via feedback from Sawyer X and Stevan Little (and a bit from Matt Trout and Peter Mottram). Also, many of the underlying ideas have been directly "liberated" from Stevan's work.

Also, note that the intent is that this will ultimately be implemented in perl, not Perl. Thus, it would be written in C and likely be much faster than current options.

ASSUMPTIONS

In creating this proposal, I assumed the following:

  • No Implementation

    This document describes a possible OO system. It does not contain information about implementation. Further, general OO "details" about how roles work, how inheritance works, and so on, are mostly omitted.

  • Feature Compatibility

    We should not take away anything core Perl 5 supports. Thus, multiple-inheritance must be supported. I considered dropping it and saying "no, you have to use single inheritance", but we have a host of popular modules, such as Catalyst and DBIx::Class, which use MI and could thus not be easily ported were the authors ever inclined to do so.

  • Simplicity

    I strove to make this proposed syntax as simple as possible. This makes implementation easier and will have fewer grounds for objections.

  • Roles Must Be Included

    Most modern Perl 5 developers who use Moose/Moo use roles. Many of them use roles heavily. Thus, they will need to be supported.

  • Lexical Scope

    If possible, if the changes suggested can only apply to a given lexical scope, I suspect it will be easier to use the new classes with old code.

  • use v5.3X;

    This would be implemented as a feature and automatically be enabled if you use use v5.3X (or similar syntax). This would avoid having to jump through special hoops to use the new OO syntax. It would simply be there.

  • Safety

    Cor roles and classes assume strict and warnings by default. They also use subroutine signatures.

  • Hash References

    Assumes we use blessed hash references for the first pass. This may be revisited in the future.

  • Role Implementation

    Role implementation assumes Traits: The Formal Model (pdf) rather than the less formal Traits: Composable Units of Behavior (pdf) that is usually cited. The authors are the same, but the "Formal Model" is explicit about several assumptions made in the better-known paper.

TRIAL GRAMMAR

Below is a minimal and almost certainly incorrect grammar as a starting point for discussion.

(*
    cheating by allowing regexes and character classes
*)

Cor         ::= CLASS | ROLE
CLASS       ::=  DESCRIPTOR? 'class' NAMESPACE VERSION? DECLARATION BLOCK?
DESCRIPTOR  ::= 'abstract'
ROLE        ::= ‘role’ NAMESPACE VERSION? DECLARATION BLOCK?
NAMESPACE   ::= IDENTIFIER { '::' IDENTIFIER } VERSION?
DECLARATION ::= { PARENTS | ROLES } | { ROLES | PARENTS }
PARENTS     ::= 'isa' NAMESPACE  { ',' NAMESPACE }
ROLES       ::= 'does' NAMESPACE { ',' NAMESPACE }
IDENTIFIER  ::= [:alpha:] {[:alnum:]}
VERSION     ::= 'v' DIGIT '.' DIGIT {DIGIT}
DIGIT       ::= [0-9]
BLOCK       ::= # Work in progress. Described below

THE PROPOSAL

The bulk of this is to simply provide two things, classes and roles. The Cor syntax is deliberately simple and would be familiar to Perl 5/6 programmers, as well as programmers of other languages (single quotes imply exact text):

DESCRIPTOR 'class' NAMESPACE VERSION? DECLARATION BLOCK?
'role' NAMESPACE VERSION? DECLARATION BLOCK?
  • DESCRIPTOR

    Optional. Currently, if present, must be the keyword abstract which indicates a class that cannot be instantiated and must be subclassed.

  • 'class' or 'role'.

    One of class or role, indicating the type of this code. Required.

  • NAMESPACE

    The name (package) of the class or role. Follows current naming rules. Required.

  • VERSION

    A v-string identifying the version of this class/role. our $VERSION = inside of the BLOCK is also still allowed. Optional.

  • DECLARATION

    This will be described later, but essentially allows us to declare what classes, if any, we inherit from, and what roles, if any, we consume.

  • BLOCK

    The block of code defining the body of the class or role.

Only the 'class'/'role' and NAMESPACE and required:

class Person;
    ...
role Comparable;
    ....

If the BLOCK is not supplied the changes are file-scoped. Otherwise, they are block-scoped:

class Person     { ... }
role  Comparable { ... }

If possible, any other syntax changes suggested by this proposal would only apply to the scope of the BLOCK or file and be an error outside of the block.

Classes

In Perl 5, classes and packages are the same thing. While this has some drawbacks, it's worked reasonably well and we'll stick with this.

Basic Syntax

Cor introduces a new, simplified syntax:

class Dog v0.1 {
    method speak () { return 'Woof!' }
}

my $dog = Dog->new;
say $dog->speak;  # prints 'Woof'

Note there is no trailing semicolon required.

Alternatively, if no arguments are required, we can omit the parens with the method keyword:

class Dog v0.1 {
    method speak { return 'Woof!' }
}

Inheritance

Declaring inheritance is done via the isa keyword and takes a comma-separated list of class names (whitespace allowed). Some restrictions:

  • Cor

    You may only inherit from Cor classes as we cannot guarantee the behavior of non-Cor classes. This restriction may be removed in the future. However, for now we would prefer to maintain this restriction to avoid the possibility that Cor and non-Cor classes might need a different UNIVERSAL base class, thus altering their behavior.

  • C3

    C3 method resolution order is assumed.

      # cannot be instantiated
      abstract class Animal {
          # forward declarations are abstract methods
          # must method body must be defined by the time its called
          method speak;
      }
    
      class Dog isa Animal {
          method speak () { return 'Woof!' }
      }
    

In the above, Dog inherits from Animal.

By using whitespace to separate the classname from the version, we can also specify versions we require:

abstract class Animal v1.9 {
    method speak;
}

class Dog isa Animal v2.0 {
    method speak { return 'Woof!' }
}

The above should work according to current Perl 5 semantics (principle of least surprise).

We can also do:

class Kill::Me::Now isa I, Despise, Multiple::Inheritance { ... }

In the above, the class Kill::Me::Now inherits from I, Despise, and Multiple::Inheritance, in that order.

Role Consumption

Classes consume roles with the does keyword:

class My::Worker does Serializable, Runnable {
    ...
}

Of course, you can combine this with inheritance:

# obviously, if My::Worker consumes these roles, we do not need to repeat
# this here. This is only an example
class My::Worker::Fast isa My::Worker does Serializable, Runnable {
    ...
}

You may specify the does before the is:

class My::Worker::Fast does Serializable, Runnable isa My::Worker {
    ...
}

We don't envision supporting excluding or renaming role methods at the start, but please see the "Future Work" section.

Methods

Methods are accessed via the method keyword. Object slots (see below) are accessed via the self keyword. Methods use signatures, but a method with no arguments (aside from the invocant) may omit the signature:

method speak { say "Woof!" }

method allowed_to_vote (@people) {
    my @voters;
    foreach my $person (@people) {
        push @voters => $person 
          if self->is_on_voter_role($person);
    }
    return @voters;
}

Further:

class Foo {
    use List::Util 'sum';
    ...
    method dimsum() { ... }
}
Foo->new->sum;

The above would issue a runtime error similar to Can't find method 'sum' because the dispatcher would recognize sum as a subroutine, not a method. Further, roles would provide methods, not subroutines. This approach should eliminate the need for namespace::autoclean and friends.

Method dispatch would be resolved via the invocant class and the method name. The arguments to the method will not be considered.

Slots

Note: "slots" are internal data for the object. They provide no public API. By not defining standard is => 'ro', is => 'rw', etc., we avoid the trap of making it natural to expose everything. Instead, just a little extra work is needed by the developer to wrap slots with methods, thereby providing an affordance to keep the public interface smaller (which is generally accepted as good OO practice).

has SLOT    OPTIONS_KV;
has [SLOTS] OPTIONS_KV;

The basic slot declaration is simple;

has 'name';

By default, all slots are read-only and required to be passed to the constructor. Thus, to create an immutable point object:

class Point {
    has [ 'x', 'y' ];
    
    method to_string {
        # self->x and self->y are not available outside of this class
        return sprintf "[%d, %d]" => self->x, self->y;
    }
}

my $point = Point->new( x => 3, y => 7 );
say $point->x;            # fatal error
say $point->to_string;    # [3, 7]
Point->new( x => 4 ); # exception thrown because y is required

To provide a default:

has days_ago => ( default => method {20} );

Also, per conversation with MST, it's possible that all default slots should be automatically lazy.

Alternatively, if the default is a string, it's a method name to call (makes it easier to subclass):

has _dbh => ( default => '_build_dbh' );
method () _build_dbh { ... }

We should separate default and builder, yes? Anything with a builder would not be passed to the constructor.

Lazy slots (requires default):

has days_ago => (
    default  => method { ... },
    lazy     => 1,
);

We may wish to make the has function extensible, so that people can experiment with isa to manage their own types.

Exposing slot data requires writing a method:

class Point {
    has [qw/x y/];

    method x {self->x}
    method y {self->y}
}

Summary of slot options:

  • default => CodRef|Str (provide a default value if one is not supplied)
  • lazy => Bool (if default is provided, don't call it until it's asked for, Default is true?)
  • weaken => Bool (weaken the reference in the slot. Default is false)
  • optional => Bool (is this required by the constructor? Default is false) (unsure about this one)
  • rw => Bool (read-write, but only via the self keyword. Default is false)

Class Construction

new()

The new method would be in UNIVERSAL::Cor and should not be overridden in a subclass, though that will be allowed.. It would take an even-sized list of key/value pairs, omitting the need for a hashref:

Object->new( this => 1, that => 2 );      # good
Object->new({ this => 1, that => 2 });    # bad

BUILD

A BUILD method, just like Moose/Moo's BUILD method, will allow for additional customization:

method BUILD (%args) {
    unless ( self->verbose xor self->silent ) {
        # Speculative. We don't address exceptions in this proposal
        self->throw("You must specify one and only one of 'verbose' or 'silent' ");
    }
}

BUILDARGS

The BUILDARGS method is how Moose/Moo messes around with arguments to allow us to do things like write this:

Point->new( $x, $y );

Instead of this:

Point->new( x => $x, y => $y );

However, the BUILDARGS method has always been a bit clumsy. We don't yet have a proposal for this.

UNIVERSAL::Cor

In order to not paint ourself into a Corner (hey, I'm a papa. I can tell bad papa jokes), we should have a separate object base class which all Cor objects implicitly inherit from. At minimum:

abstract class UNIVERSAL::Cor v.01 {
    method new(%args)   { ... }
    method can ()       { ... }
    method does ()      { ... }
    method isa ()       { ... }
}

That mirrors the UNIVERSAL class we currently inherit from, but there's room for more:

abstract class UNIVERSAL::Cor v.01 {
    method new(%args)   { ... }
    method can ()       { ... }
    method does ()      { ... }
    method isa ()       { ... }

    # these new methods are merely being mentioned, not
    # suggested. All can be overridden
    method to_string ()    { ... }    # overloaded?
    method clone ()        { ... }    # (shallow?)
    method object_id ()    { ... }
    method meta ()         { ... }
    method equals ()       { ... }
    method dump ()         { ... }
    method throw($message) { ... }
}

This is still an open discussion. We don't want to pack too much into the API and cause developers pain, but there are so many "common" use cases for objects that we're tired of rewriting ad nauseum that, like many other programming languages, it might be reasonable to put them into the base class.

Opinions welcome. However, this is such a core (no pun intended) needs that understanding if we need a separate UNIVERSAL class for Cor should be decided before Cor is ready for prime time (see also, Stevan Little's UNIVERSAL::Object).

This, incidentally, is why Cor classes cannot inherit from non-Cor classes and vice-versa.

EXAMPLE

Before

Here's a simple LRU cache in Moose:

package Cache::LRU {
    use Moose;
    use Hash::Ordered;
    use namespace::autoclean;

    has '_cache' => (
        isa     => 'Hash::Ordered',
        default => sub { Hash::Ordered->new },
    );

    has 'max_size' => (
        default => 20,
    );

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

    __PACKAGE__->meta->make_immutable;
}

After

Here it is in Cor:

class Cache::LRU {
    use Hash::Ordered;
    has cache    => ( default => method { Hash::Ordered->new } );
    has max_size => ( default => method { 20 } );

    method set ( $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 );
    }

    method get ($key) { self->cache->get($key) }
}

Note that in the Cor version, any attempt do directly access the cache or max_size slots from outside the class via direct access is an error, though you can override them:

my $cache        = Cache::LRU->new( max_size => 100 );
my $hash_ordered = $cache->cache;    # fatal error

Roles

Basic Syntax

Role syntax is also simple and clear:

role Whiny {
    method whine($message) { ... }
}

class My::Class does Whiny {
    ...
}
My::Class->new->whine('some message');

Roles both provide and require methods. Any methods fully defined in the role body will be composed into the consuming class.

Any methods defined via a forward declaration are "required" to be provided by the consuming class or another role consumed by the same class.

role MyRole {
    method this;                 # class must provide this
    method that;                 # class must provide this
    method foo ($bar) { ... }    # this is provided
}

Like classes, roles may also have slots in the same matter as classes and those slots will be provided to the class. (What happens if the class defines that slot in a different way from the role? For example, if the role slot is read-write but the class slot is read-only, bugs are awaitin').

Of course, roles can consume other roles:

 role SomeRole does ThisRole, ThatRole { ... }

Strictly adhering to the concept that roles are guaranteed to be both commutative (the order of application doesn't matter) and associative (for a given set of roles, it doesn't matter which consumes what, so long as the final set is the same), the above is equivalent to:

 role ThatRole does SomeRole, ThisRole { ... }

And their aggregate behavior will be the same if one or more is consumes other roles and are in turn consumed by a class:

role ThatRole   does ThisRole { ... }
role SomeRole   does ThatRole { ... }
class SomeClass does SomeRole { ... }   # role-provided behaviors are identical

MOP

The MOP module is likely sufficient for our needs.

TODO

For the first pass, we need:

Clarify Syntax

Is the current proposed syntax acceptable? I argue that it is because I feel that it still feels "Perlish", while also feeling clean enough that developers from other languages will feel right at home.

Questions

Read-only slots with no values

This is problematic:

has 'x';

The above is a private, read-only slot with no value. Unless we require it to be passed to the constructor, it's useless and should possibly be an error. This is where our BUILDARGS work (or replacement) might come in handy. It would be good if behaviors are specified declaratively, rather than procedurally.

Class-Level Behaviors

How do we handle class data and methods?

Some argue that class data is a code smell. Fine: argue that all you want. Multiple inheritance is also a code smell, but that doesn't mean we can tell Perl developers "no." But the semantics of that can get tricky.

Class methods, however, are important. For example, you may very well want a factory class with an interface like this:

my $message = Message::Factory->create(@message_list);

Internally:

static method create (@list) {
    if ( 1 == @list ) {
        return Message::String->new( message => $list[0] );
    }
    else {
        return Message::Collection->new( messages => \@list );
    }
}

Inside that method, any attempt to call a method on the C keyword would be a syntax error. It could internally call other static (class) methods directly (?) without an invocant.

"Why only blessed hashes?"

For simplicity. We currently don't have a clear vision of how non-hashrefs can be done transparently with this. Falling back to core OO may be a solution for some. For those who know they need a blessed regex, they'll (hopefully) know enough about core OO to go ahead and run with scissors.

"Why don't we have method modifiers?"

Cor tries to be as small as possible to avoid overreach. That means "no modifiers" at this time. However, they cause an issue for roles.

Let's say a method returns the number 10. One role modifies that number by adding a 20% VAT, making the result 12. Another role modifies that to offer a discount of 3, making the result 9. However, if the discount is applied and then the VAT is added, the result is 8.4. Thus, a developer could sort the list of consumed roles and change the behavior.

In the original traits research, one of the issues they were trying to work around was the fact that inheritance order could change code behavior. Consumption of roles, however, were guaranteed to be both commutative (the order of application doesn't matter) and associative (for a given set of roles, it doesn't matter which consumes what, so long as the final set is the same). Method modifiers break this guarantee.

Future Work

I believe the initial core of Cor should be as simple as we can possibly make it to avoid too much up front work and possibly making mistakes that we cannot walk back. However, any good design of something that is both long-lasting and that we know will grow should at least be aware of future considerations. Otherwise, we might make it harder to address issues.

Types

This will wait until the core OO is there. Making has extensible might help.

Parameterized Roles

I have no suggested syntax for this, but they're extremely useful.

Role Exclusion and Renaming

Matt Trout argues, and I agree, that excluding role methods or renaming them is a code smell. However, if you don't have control over the role source code (downloading from the CPAN or being supplied by another software team), you don't always have the luxury of refactoring the code. Thus, we need to support excluding methods and renaming them.

The syntax for this is less clear at this time, but I envision something like this:

class My::Worker isa Some::Parent::Class
 does Serializable(
    excludes => ['some_method'],
    renames  => { old_method => 'new_method' } ) {

    method some_method { ... }

    method old_method (@args) { ... }
        # do something with @args
        return self->new_method(@args);
    }
}

Excluding or renaming a method automatically makes it a "required" method. This is because, even if you don't use them in your class, the role might use them internally.

This raises an issue. In the original Smalltalk traits papers, they made it clear that a role is defined by its name and the methods it supplies (methods are defined by their signature, not just the name). It's possible that someone might do this:

if ( $object->DOES('Serializable') ) {
    ...
}

At this point, we don't know if the $object class excluded any methods from the Serializable role. Thus, we don't know if any methods we expect from Serializable will conform to expectations. Thus, the naïve Does('Serializable') check may be wrong because merely having the role name isn't enough to know if the class exhibits the desired behavior.

In proper OO, the replacement methods should be semantically identical, even if they're doing different things. In reality, we know that these guarantees are often tossed out the window. I don't know that this is really a serious issue because I haven't been hit with this, but I also know that safety in building large scalable systems suggests avoiding pitfalls.

I do not have a recommendation for this, but I point it out so people can be aware of the background.

Runtime Role Application

I have no suggested syntax at this time, but this generally involves reblessing an object into an anonymous subclass which consumes the role or roles. Naturally, it's harder to guarantee object behavior, especially if several roles are applied at runtime in separate statements.

ACKNOWLEDGEMENTS

Stevan Little has been working on an object system for Perl for years. And given his background—including creating Moose and Moxie—and his constant research into a "better" way to write OO, he laid much of the groundwork for Cor.

I had been working on a pure-Perl implementation of Cor (because clearly we don't have enough object modules on the CPAN) and discussing it with the Pumpking, Sawyer, at the 2019 EU Perl Conference in Riga, when he said he wanted a spec, not an implementation.

And he's right: with P5P, there are plenty of implementors, but there's been no agreement about what should be implemented. So, working with Stevan and Sawyer, I've had to suffer the humiliation of them laughing at my amateurish mistakes, but it's made this document better as a result.

Any mistakes, of course, are mine.

UPDATE

Putting updates here so they can be easily spotted without consulting the history.

self as a keyword

What does this do?

method foo {
    some_external_function(self);
}

We can add a check on self to ensure that private slots cannot be accessed unless we're in a class or a subclass of ref self, but seems clumsy. Or we can tell developers "don't do that", but we all know what that means.

If we remove self, we need an easy way for the class to access its internal state. Lexical variables have also been proposed:

class Foo {
    has $x => ( rw => 1 );
    
    method bar ($new_x) {
        $x = $new_x;
    }
}

Feels unperlish to me, but hey, what do I know? :)

BUILDARGS

We're trying to figure out a better syntax.

@jplindstrom

This comment has been minimized.

Copy link

jplindstrom commented Oct 16, 2019

I can't see any example of what "Instead, just a little extra work is needed by the developer to wrap slots with methods" would look like. Please update with realistic examples of the effect of the design.

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

jplindstrom: good point.

Here's what we've been looking at:

class Point {
    has [qw/x y/];

    method x {self->x}
    method y {self->y}
}

I resisted this (hard) at first, until Sawyer and Stevan beat me into submission by pointing out a few things.

  • Most slot data is (and should be) internal, but Moose/Moo classes tend to automatically expose the slots via is => .... Thus, while objects shouldn't be exposing too much, Moose/Moo offer an affordance for this unwanted behavior.
  • I had a bunch of stuff written about slot optional and how they would combine (reader, writer, is, etc), and it was pointed out that most of the complications I was finding were a side effect of automatically creating methods for slots.
  • The Cache::LRU example I provided accidentally demonstrates this: we have two public methods, get and set. That's all we needed.

By requiring people to explicitly write methods to expose the slot data, we force them to do a little more work. They might be inclined to not do that work and thus not expose things that need not be exposed after all.

@jplindstrom

This comment has been minimized.

Copy link

jplindstrom commented Oct 16, 2019

I almost never use `is => "rw", but getters are somewhat common.

That's not a design smell, especially not when the attribute value is the outcome of a lazy builder based on other things. This is in fact turned out to be one of the nicest idioms enabled by Moose.

So when I do need any of these, the price for opinionated affordance shouldn't be clumsy extra lines of code.

For the Point example above, can you please show an example of a single method getter/setter for "x"? (the get_x/set_x pattern isn't used in almost any Perl code)

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

There were a several design goals we had in mind. Two of these were:

  • Make immutability a natural default
  • Make small interfaces the default

The benefits of immutable objects have been discussed at length elsewhere, so I won't belabor that point, but you can read about the pain of DateTime's mutability here (it used to bite us on the taustation.space MMORPG, too, until we switched to our internal Veure::DateTime::Immutable class).

Small interfaces are harder to explain to OO devs.

Consider the Cache::LRU example I gave above. Calling $cache->cache is a fatal error because I provided no accessor for the internal Hash::Ordered cache. That cache, of course, is a reference and making it immutable would require constantly instantiating new versions of it with the new state, killing the performance goal that a cache is supposed to bring.

But so what? What if we allowed $cache->cache? Well, since that's returning a reference, any other place in the code might alter its state and that's a devilishly hard bug to track down.

But what about minor things like $point->x? Surely that's not a reference? In this case, it's not, but we're trying very hard to promote the idea that slots and their accessors are separate things. I repeatedly find myself working with classes that have "public" methods that are always available, but never called outside of a class. When that happens, I make them private so that I don't have to worry about the "contract" I've established with code outside my class. That gives me the freedom, later, to change the internal state at will without accidentally breaking promises I've made to those consuming instances of the class.

Instead, Perl developers using Moose just do this out of habit:

has some_slot => ( is => 'ro', isa => 'HashRef' );

But of course, you're reading a mutable reference. Calling it "read-only" is almost useless. But the way that Perl developers do "OO" is treating them as glorified structs with extra behavior attached, instead of experts about a problem domain.

But for $point->x, if you want it mutable, you can do this:

class Point {
    has [qw/x y/] => ( rw => 1 );

    method x ($x = undef) {defined $x ? self->x($x) : self->x}
    method y ($y = undef) {defined $y ? self->y($y) : self->y}
}

In this particular case, you may not want the mutability, but hey, you can have it if you want to. And a Point class is, naturally, such a trivial example that it winds up being a counter-example.

But imagine a more complicated caching class, one that tracks not only the data, but the data which is most commonly accessed? Now you need to track the keys and the relative frequency by which they're accessed? Do you want to expose that state too? In fact, no. You might find different data structures useful, depending on how want to manage your caching. For example, you might want a minimum bucket size. You might want a maximum bucket size (for shaping the cache, not caching the keys). So now you have the internal cache, the max size, minimum and maximum bucket sizes, the bucket structure, and possibly more. But the only interface you need to expose is get and set.

Thus, we offer a small speed bump to make developers think about whether or not they should expose their slot data.

I also strong recommend watching How Moose made me a bad OO programmer by Tadeusz Sośnierz.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

You don't have an example of what it looks like to set a new value for a slot (from within the class I imagine). Can I strongly suggest that they should look like lvalues, thus allowing any of the lvalue-like expressions:

self->x = 0;
self->count += $n;
self->caption .= " (see page 1)";
self->title =~ s/mr/Mr/;
substr(self->name, 16) = "";

Additionally this would allow a particularly brave class to just expose that lvalueness to callers:

method count :lvalue { self->count }
@ferki

This comment has been minimized.

Copy link

ferki commented Oct 16, 2019

Looks like the MOP link points to https://metacpan.org/release which gives me 404. Perhaps it was meant to point to https://metacpan.org/pod/MOP instead.

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

It's worth noting that Cor may need to consider the difference between internal state of the object and external state which needs to be passed in to the constructor. It's a hard problem.

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

Looks like the MOP link points to https://metacpan.org/release which gives me 404. Perhaps it was meant to point to https://metacpan.org/pod/MOP instead.

@ferki Fixed. Thanks!

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

You don't have an example of what it looks like to set a new value for a slot (from within the class I imagine). Can I strongly suggest that they should look like lvalues, thus allowing any of the lvalue-like expressions:

self->x = 0;
self->count += $n;
self->caption .= " (see page 1)";
self->title =~ s/mr/Mr/;
substr(self->name, 16) = "";
...

@leonerd: lvalues have been deliberately avoided to keep both the implementation and the usage of Cor as simple as possible. The simpler we keep Cor, the more likely it is that we can get it into core.

But it's something which could be considered for a later release.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

A question on slots-vs-methods: If self->x fetches my own internal slot value, but someone else calling $point->x invokes the method called x, what does it mean for code within the class itself to invoke slot-or-method-namey things on other instances of its own class?

I.e. what does this program output:

class Point {
    has 'x';
    method x { say "Accessing x of ", self; self->x }
    method cmp($other) { return self->x <=> $other->x }
}

Point->new(x => 10)->cmp(Point->new(x => 20)};

Does it print the Accessing... string?

If it does, then what about:

    method cmp($other) { my $self = self; $self->x <=> $other->x }

Does that access via $self->x hit the method x or the internal slot x?

Overall it may be a little bit too subtle to have the same-looking syntax for what is two very different things.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

@leonerd: lvalues have been deliberately avoided to keep both the implementation and the usage of Cor as simple as possible. The simpler we keep Cor, the more likely it is that we can get it into core.

@Ovid: Then if you has x => rw => true, how does the x slot get modified at all? There's no example of a modification of that in any form.

@xenu

This comment has been minimized.

Copy link

xenu commented Oct 16, 2019

The block after class/role isn't required, right? I hate the idea of having to pointlessly indent the whole file by one level.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

The block after class/role isn't required, right? I hate the idea of having to pointlessly indent the whole file by one level.

@xenu:

If the BLOCK is not supplied the changes are file-scoped. Otherwise, they are block-scoped:

I believe it behaves the same as the package keyword; e.g.

class Point;
has 'x'; has 'y';
method foo ...
@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

It may be a rehash of old conversations (though there's no history here on this proposal so hard to see what came before), but I wonder if you have considered the advantages of making slots look like lexical variables? In summary I'd like to propose a twigil-based notation using $.name:

class Point {
    has $.x; has $.y;
    method where { say "I am at ($.x, $.y)"; }
    method move_to_origin { $.x = 0; $.y = 0; }
}

From here several advantages become clear, as member variables:

  • can be assigned to or otherwise behave like normal lvalues ($.count++)
  • interpolate in strings as per usual
  • could also be arrays or hashes (has @.items; has %.data)
  • behave like regular variables (my $href = \%.data; $href->{key} = $value;)
  • look obviously very different from methods (method x { $.x })
  • become subject to strict checking - say "I'm at ($.x,$.y,$.z)" will fail at compiletime
  • are lexically scoped to the class {} block and so "obviously" remain private and not inherited by subclasses

As a consequence of this last point I feel they are a lot safer for subclassing. I can never break a subclass by adding a new member variable to any of its parent classes, because even if the name is the same it is lexically scoped differently and so cannot collide.

Now I picked . as a twigil fairly arbitrarily - I think it looks neat and doesn't collide with any other valid syntax, but I could be swayed if other characters look better. I also find in practice the current punctuation var of $. comes up sufficiently rarely in real OO code that there's not too much of a readability problem in conflicting the two.

One thing I would say is, having more than a passing familiarity with the core innards on how to implement something like this - don't be afraid of considering it "because it might be harder to implement". I've thought about this kind of thing for quite a while and actually I think this may be easier to implement than the various questions and ambiguities around the self->name proposal instead.

Now I will admit that trying to experiment with this idea in a non-core XS module is unlikely to work - even PL_keyword_plugin is not this powerful. It really would need to be done in core - but I think it could be done as easily, if not easier to get all of the above properties, in this form than self->name.

@latk

This comment has been minimized.

Copy link

latk commented Oct 16, 2019

This proposal introduces a number of keywords, which seems to run against the goal of being a minimal proposal. What is their rationale?

  • self: why not a variable $self? Using a keyword does make it easier to do static checks, e.g. disallowing access to the underlying hashref but allowing access to private slots. However, would such checks not also be possible when using normal variable syntax? Using a keyword makes string interpolation more difficult, is this an acceptable tradeoff?

  • method: why not sub? I do not see the value added by this new keyword, as method foo($x) { ... } and sub foo($self, $x) { ... } would end up being the same thing. Would a sub within a class not be callable as a method? Do you want a syntactic escape hatch in order to remove cruft around the sub syntax (like prototypes)? How would a separate method keyword mesh with concepts like class methods, would those require new keywords?

  • abstract: putting this in front of class effectively turns this into a global keyword. Wouldn't it be better to put it into a place where it can't conflict, e.g. class Foo is abstract?

  • Should additional keywords within the block be reserved in advance in order to allow future extensions? E.g. super, before, around, private, meta, classmethod, …. Any core syntax for objects would be a huge ergonomics improvement, but it should not design Perl into a corner.

Other notes:

  • This proposal includes both new syntax and new semantics. Would it make sense to separate them to some degree, so that (a) the new syntax can be explained by simple desugaring, and (b) the new object semantics can be used by existing systems such as Moo? For example, class Foo { has 'a'; method get { self->a } } might desugar as BEGIN { package Foo; use FancyNewObjects::Class; has 'a'; sub get($self) { &CORE::get_slot(__PACKAGE__, $self, 'a') } }.

  • Since the class and role keywords will be new ways to declare packages, would it make sense to use this opportunity to introduce relative package names? Currently, package names must be fully qualified: package Foo::Bar::Baz. It would be more convenient to declare classes (especially multiple helper classes in a file) when you'd just have to do package Foo::Bar; role Baz { ... }.

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

@leonard: twigils were discussed several times, but it seems the majority of people have been dead set against them with the argument that introducing even more punctuation doesn't help Perl's reputation :)

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

it seems the majority of people have been dead set against them with the argument that introducing even more punctuation doesn't help Perl's reputation :)

@Ovid: Yes; while I can see the argument there, my answer to those people would be to consider the wider scenario. What would be easier to explain?

  • $.name - ohyes think a bit like self.name and... that's it. (Another reason for picking .)
  • It behaves exactly like a normal my variable in any other way (mutation/lvalue, referencing, string interpolation, array/hash access, ...)

vs

  • Right so assigning to these self->x things looks totally different,
  • You can't use them in "quoted strings"
  • If you want to store an array or a hash you'll have to use a reference and deref them all the time
  • Plus you now still have to explain that self->x vs my $self = self; $self->x are going to do very different things. I don't know how I would begin to explain my way out of that one. I don't even fully follow the proposal myself on how they might differ so I definitely couldn't teach a perl newbie. That problem does not exist for twigils.
@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

If people are really so set against the idea of the interposed . then I could be happy with

class Point {
    has $x; has $y;
    method where ( say "I am at ($x, $y)" }
}

without the .. That does add a little bit of cognative load on the programmer and reader who now have to keep track of which lexical-like-variables come from the object vs. which are real lexicals, but that's something that e.g. C++ or Java programmers have to do anyway and they seem to cope well enough. So perhaps it would be liveable without the twigil dot.

As I said I'm not overly fixed on the dot itself - the central idea of mine was "they behave like lexicals".

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

In fact.... The more I think about it, the more I think I could provide an XS module for current perl to experiment with this very idea, if we don't use the twigil dot. An actual-core implementation might be able to give it a small performance boost at method-call time, but the basic structure would work fine, and certainly allow experiment with the syntax and the overall feel of it.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

I think there are a lot of good ideas here, but there needs to be simple ways to avoid having to write any boilerplate to get either read-only or read-write accessors. Otherwise nobody will use it. I'd also point out that an accessor cannot rely on defined to determine whether it was called as a setter.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

Another bonus mark for lexical-like slots (using twigils or otherwise) is that captured lexicals inside closures (e.g. callbacks in a method) will work.

class Counter {
    has $count = 0;
    method sproing($sproinger) {
        $sproinger->do_thing->then( sub { $count++ } );
    }
}

whereas if that had to be self->count++ or even self->count(self->count+1) I can't see how the self keyword is going to work inside that non-methodical sub { } callback.

@Ovid

This comment has been minimized.

Copy link
Owner Author

Ovid commented Oct 16, 2019

@leonerd:

You know, you're really being a pain about this. Thank you for that :)

@Grinnz:

The lack of built-in accessors is the part I objected to and finally relented on after deep discussion. I need to write more about this, but I realize that community demand and properly structured OO may be at odds with one another.

And regarding "defined", I forgot to include the bit in my proposal that we need to have a clear way of distinguishing between "defined" and "initialized", because if something is uninitialized, it's going to be undef, but if it's initialized to undef, that's OK.

Everyone else: keep those comments coming in. We're reading 'em!

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

IMO, "properly structured OO" should not be a design goal. The goal should be providing the tools that people want. Sure, immutable state is a great idea in a lot of cases, but it's also unhelpful in a lot of others, and irrelevant in still others. All this would do is force people to write the same boilerplate they have to write with core OO, where the point of this should be making common tasks declarative. I have no problem with making private slots "easiest", but I have a problem with not making accessors easy.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 16, 2019

  • A couple of format tweaks here: https://gist.github.com/matthewpersico/b6757b1ae9fdedf73030ff34e34b87de
  • I think that a paragraph on the difference between method and sub would be useful. If I had to take a stab at it, I would guess that method bakes in self. Hmm. Maybe sub can be used for static class methods?
  • I've gone round and round on the "sigilless self" vs "$." argument in my head. I've read @leonard 's comments. If all of his four points in the second half of the "vs" are true, then maybe we can get away with using the '.' twigle if we just don't call this a 'twigle', implying we can apply twigles willy-nilly everywhere. Let's just call this a new syntax for Perl, version 7.0 (yeah, I went there; I'll shut up now) and just say, "This is how we now spell the no-need-to-unpack-it-yourself-self."
  • Among the slot options is
    rw => Bool (read-write, but only via the self keyword. Default is false)
    With a default of false, does this mean that once a slot value is set with the constructor, it is also immutable though self|$.?
  • I'd like to suggest another slot option:
    accessor => r[o|w] (generate an public accessor function in the given mode)
    IMNSHO, without this option, multiple generations of Moose users are going to be pissed off that they now have to hand roll their own accessor functions that they had previously gotten for free, which in turn will most certainly reduce the uptake on porting to Cor. At least with this option, they can mechanically translate their existing has statements in their code into this this slot option and not have to hand-code/generate new code.

In general, I think this is a fantastic proposal. It is minimal, yet comprehensive. Great work.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

The bit about defined vs initialized is also important, but not what I was referring to regarding the accessor. The only way to distinguish an accessor being called as a getter vs a setter is number of arguments.

@autarch

This comment has been minimized.

Copy link

autarch commented Oct 16, 2019

A big ++ on the idea of using lexicals for the slots. It seems like a natural fit with Perl's existing syntax and solves a number of problems that @leonerd noted.

I also agree with @Grinnz that having a simple way to declare read-only accessors is extremely important, but I'm fine with read-write being more cumbersome.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

Whatever the method, read-write accessors should be declaratively possible, simply because otherwise people will reimplement them wrong, exactly as was done in the example here.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 16, 2019

Ok, after reading a few comments that came in while I was polishing my prior comment, I would like to object (intellectually, nor forcefully with a pitchfork) to the simple has $x syntax. I believe it will be too easy to lose track of what's a slot and what's a variable deep inside a method when they both look the same. (Is that one of Larry's ideals, that things should look different that are different?) The slot should really look different, whether that's self or $.. And the more I think about it, the more I like has $.x; it is minimal enough so as to be efficient and also not jarring to both old and new Perl eyes, and it "looks" like Perl more than self does. The unadorned self in the proposal really did throw me for a loop on first reading.

For my money, if we end up using has $x, then my Cor classes are going to look like:

class Point {
    has $O_x; has $O_y;
    method where { say "I am at ($O_x, $O_y)"; }
    method move_to_origin { $O_x = 0; $O_y = 0; }
}

where O makes me think of Object. Or I would use M for Member. Or maybe S for Slot. Or maybe a lowercase letter. Something, anything to make the object's data stand out when I debugging an issue at 2am. I don't think I would be alone.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 16, 2019

@Grinnz, @autarch and anyone else who wants an idea for an accessor declaration, see https://gist.github.com/Ovid/68b33259cb81c01f9a51612c7a294ede#gistcomment-3057032

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

Strange that GitHub does not allow reactions on gist comments, but the declarative accessor you proposed would be fine with me.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

@matthewpersico - yes indeed that is what I called the "C++ or Java issue" - the one wherein you can't tell at a glance what is a lexical variable vs. a member slot. A twigil would solve that. But lacking twigils I've often seen a strong naming scheme used much as you suggest - e.g. at Google it was a code requirement that all member variables are named mFoo, and no function parameter is named such. It becomes just a slight consideration about readability at that point.

With my own selfish oar in the mix though I'll point out that without twigils I could implement this feature as a regular keyword-using XS module on existing Perls - maybe back as far as 5.14 - but requiring twigils would force it into being core-only for some future version. Maybe they could be optional? ;)

@Corion

This comment has been minimized.

Copy link

Corion commented Oct 16, 2019

This is a quick reply, so maybe I have overlooked the crucial detail that brings this aspect:

I think this proposal mostly keeps the bad thing of the Moo* family while not bringing the good thing. It has no good way of setting up the behaviour of constructor arguments except stuffing them into slots, and declaring an accessor for that slot. This will invariably end up with every potential parameter being stuffed into an slot, no matter whether it makes sense for the object life time to have that data available at all. Moo* provide the coerce option for modifying how the constructor determines the value for this slot. Having (something like) this prevents the bad habit of gratuitious slots. See also the discussion at https://perlmonks.org/?node_id=11107488, and the video by Tadeusz for more exposition of the bad habit, and how coerce helps.

I understand that I can write the appropriate coercions in BUILDARGS myself, but I think the approach should be to make the correct thing easy, and writing things in BUILDARGS seems less easy than creating a fake slot, just to have Perl put the constructor arguments somewhere a lazy attribute can pick them up later.

@shadowcat-mst

This comment has been minimized.

Copy link

shadowcat-mst commented Oct 16, 2019

I'd really like lexicals, especially since the idea LeoNerd and I cooked up makes it look like it could be done on CPAN as well, and damnit the self->x thing makes bunny cry.

I get the thing about trying to convince people to not publicise stuff that doesn't need to be ... however the billion Class::Accessor like packages on CPAN make it very clear people hate writing their own accessors, and also one of the really nice things about using M* OO, for me, is that accessors are standardised and predictable. The old proposal of "provide an is to make it public" seems better to me, because not having standardised accessors at all will (IMNSHO) inevitably result in a mixture of upset users and inconsistent interfaces.

Wrt naming, yeah, I can see the disambiguation thing; in previous experiments I've tended to do 'has $_x;' - effectively using _ as a faux twigil to indicate "this is the internal _x of the object ala $self->_x". This may or may not seem remotely sensible to anybody else.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

I don't think it is worth discussing method exclusion or renaming from roles at all in this proposal. As noted it defeats the usefulness of DOES checking. It is not something anyone is doing on CPAN and there's a good reason for that.

@perigrin

This comment has been minimized.

Copy link

perigrin commented Oct 16, 2019

So I'll leave my concerns about self to @leonerd who is doing a much better job than I would poking the holes in it that I see (and several I didn't). Same with @latk's comments about the other keywords.

There is a lot of new syntax in here. Every single one of those keywords is gonna be opined on in depth by this community and then again when it gets to p5p. But you're probably as aware of that as I am, and honestly at this point I have some level of Stockholm Syndrome about it and suspect that the conservative nature of that process is probably good for the language.

But there a point that I think will kill this proposal on arrival. (Something I really don't want to see)

This, incidentally, is why Cor classes cannot inherit from non-Cor classes and vice-versa.

Cor is in effect an entirely new Object System. Okay I'm with you, break back-compat, Vive le nouveau! But...

Falling back to core OO may be a solution for some.

The old Perl5 OO system still exists, and so has to be maintained. How long do we maintain both systems? Is it officially deprecated in the officially core sense of deprecation (with a scheduled removal date)? If it's not removed how do we explain to the new people we're trying to attract with our shiny new core OO system that sometimes your core OO Perl class cannot inherit from an entirely different core OO Perl class? (That seems way more problematic than twigils IMO)

I think we need to do some design work on making sure that UNIVERSAL::Cor or UNIVERSAL::Object or whatever it is as compatible as possible with existing code (blessed regexes, arrays, and subroutines included if possible) and simply replace the entire core OO rather than maintain two systems. Whatever we can't provide shims for in the new world, we simply break cleanly and be up front about that.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

I don't think we need to render any judgment on the current core OO system. It is used extensively, in particular to support other OO systems, and presumably internally for this one, and we should strive to allow all of these to continue to coexist (if not always interoperate yet) as they have been.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 16, 2019

@leonerd said:

With my own selfish oar in the mix though I'll point out that without twigils I could implement this feature as a regular keyword-using XS module on existing Perls - maybe back as far as 5.14 - but requiring twigils would force it into being core-only for some future version. Maybe they could be optional? ;)

My take is that the main point of this exercise is getting real-live-OO into core; a compromise for the purpose of not needing it in core kind of defeats that.

Now, perhaps, dropping the sigil and creating an XS for demo/prototyping purposes quickly could be very useful. But I would not let that into the CPAN^H^H^H^H wild; if you do, we'll never pull it back. :-)

@shadowcat-mst

This comment has been minimized.

Copy link

shadowcat-mst commented Oct 16, 2019

I'd note that TclOO being added to their core didn't kill the other object systems particularly, and so I'm not that troubled about it. Within any given project, most of my Moo classes tend not to 'extends' or 'with' anything outside the project, so I wonder if that worry is overblown.

@shadowcat-mst

This comment has been minimized.

Copy link

shadowcat-mst commented Oct 16, 2019

Though the effect on CPAN authors could be aggravating. A lot of work went into Moo to make it interoperate with everything.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

My suggestion for BUILDARGS would be to look at and learn from the problems it has in existing implementations. My main problem with it is that overriding it requires to reimplement the "constructor can take list or hashref" logic (which can be avoided using method modifiers, but that's not currently an option here). But overriding it is also the main mechanism by which you can define a constructor that takes arguments differently. I am not sure the best way to resolve this, but one way might be to make the "list or hashref" functionality declarative on the class, and separately BUILDARGS can then operate on the result of that. The problem with this is then it's unclear how it's affected by inheritance. Another option of course is to ditch the "list or hashref" functionality entirely, and only take lists (this seems to be the most common in my experience). Which can then be overridden with BUILDARGS if desired, but a declarative way might be better. Sorry for the stream of consciousness, but I really don't know the answer here.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

Rereading I discovered that the constructor is already meant to be list-only, so that should be a non-issue.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 16, 2019

@Corion - If my suggested accessor slot option above were amended to read

accessor => 'r[o|w]' 
            # Generate an public accessor function in the given mode.
            # If r/w, it takes one argument, whose type is either of the same type as the slot, or a reference to that type, if the type is a container (non-scalar), such as @ or % or a class.
or
accessor => method { ... } | \
            q(method_name_provided_later_in_the_code) 
            # The class creator will provide a public accessor function. Class creator is responsible for unpacking its arguments, enforcing r/o if desired; `self/$.` is available.

would that suffice (not accounting for ease of actually implementing it)?

@trwyant

This comment has been minimized.

Copy link

trwyant commented Oct 16, 2019

Random thoughts:

  • Thank you for not making new() part of the syntax. I consider it, pragmatically, a strength of the Perl O-O system that new() is simply another static method. At need, it can be a factory, and you don't need to do the Java thing of figuring out what the factory method is for an abstract class like System that can not be instantiated directly. Or deprecating new() when a factory can do it better, as Java is doing now with some of the core classes like Integer.

  • My personal preference is for sigils for scalar-like things like $self. I'm not real fond of twigils, though if the alternative turned out to be something like ${^self} I think I could become fond of them fairly quickly.

  • Do you see a need for $class (however it is spelled) for static methods? I believe they were called "code smell" above, but the factory pattern is well-established.

@perigrin

This comment has been minimized.

Copy link

perigrin commented Oct 16, 2019

@Grinnz

I don't think we need to render any judgment on the current core OO system.

I don't think we have a choice there. People who come to a Perl where this is a core feature of the language are gonna be rendering the judgement.

NewDeveloper:  Why does my code blow up when I try to do `extends "HTTP::Tiny";` [ed. assuming HTTP::Tiny isn't upgraded]
#perl-help: Well that's using the old OO system, it's not compatible with the new one 
NewDeveloper: oh! where is that documented? Should I be using that instead? 
#perl-help: We changed the documents over when the people kept asking why we had two different OO systems documented.

@shadowcat-mst

Though the effect on CPAN authors could be aggravating. A lot of work went into Moo to make it interoperate with everything.

Kinda exactly my point actually. This is gonna get bikeshed-ed, that's unavoidable, but hinging on having two incompatible core systems is I think a shed colour nobody wants. We need to figure out how much of that work should be done and just "embrace and extend" the core system rather than have two incompatible core systems.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 16, 2019

We need to figure out how much of that work should be done and just "embrace and extend" the core system rather than have two incompatible core systems.

@perigrin I think you're right. It ought to be possible to mix them both ways - a new-style class extended in old style, or vice versa. You can see in some very early Python 2 the problems of not allowing that to happen. When they added what they called new-style classes, they were distinct from the old ones. It would be good for us to try to avoid that if we possibly can.

@rkleemann

This comment has been minimized.

Copy link

rkleemann commented Oct 16, 2019

Some random thoughts:

  • Consider a different twigil than period. I know that I use or run across uses of $. on occasion, whereas $: I've never seen used in production code. $-> was another thought that I briefly entertained.
  • Perhaps only allow tiwgil access for simple scalars (numbers, strings, and undef)?
  • The idea of setters being lvalues is appealing, thought perhaps only for scalar values.- Boilerplate for getters and setters suck. When ack-ing for where a value is touched, getting boilerplate code as part of the result doesn't help find the problem any quicker.
  • I tend to hate Boolean options: foo => 1, bar => 0. I would rather have something to declare them more easily: opts(qw( foo nobar )) (or spell it as no-bar or no_bar).
@DrHyde

This comment has been minimized.

Copy link

DrHyde commented Oct 16, 2019

Just doing hash-ish data structures to start with is good. KISS. If people really want a blessed code-ref they'll still be able to do that the hard way.

I presume that meta is intended for reflection/introspection? I think that's absolutely essential, to be able to easily find out what methods some random object/class has.

Having slots private by default is doubleplusgood, it would prevent many of my egregious hacks :-) But to avoid masses of boilerplate there should also be an easy way to make basic accessors for (a subset of) them. meta would presumably make it easy to write a role that does this, provided that method works at runtime and can take an expression for the name.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

This brings an interesting question: should private slots be accessible to roles? To subclasses? This would be a decidedly non-lexical scope.

@DrHyde

This comment has been minimized.

Copy link

DrHyde commented Oct 16, 2019

This brings an interesting question: should private slots be accessible to roles? To subclasses? This would be a decidedly non-lexical scope.

That is an interesting question :-) My instinct is Hell No for sub-classes, and yes for roles.

Sub-classes are something that other people write after you've written your class, and shouldn't care about the implementation of the super-class as you are free to change the implementation however you fancy - indeed in my own largest mess o' code on the CPAN the implementation has changed radically at least three times but sub-classes just carry on working because they don't care. It's up to them to conform to your requirements.

For roles I think it's different. Roles are something that either you write yourself to share functionality amongst many of your own classes, or something that someone else has already written and it's up to you to conform to their requirements.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

For roles I think it's different. Roles are something that either you write yourself to share functionality amongst many of your own classes, or something that someone else has already written and it's up to you to conform to their requirements.

I disagree. I have several roles in use that are explicitly written to use with someone else's class.

@larryl

This comment has been minimized.

Copy link

larryl commented Oct 16, 2019

Alternatively, if the default is a string, it's a method name to call (makes it easier to subclass)

How do you distinguish between the following?:

has _dbh       => ( default => '_build_dbh' );
has shirt_size => ( default => 'medium'     );

method _build_dbh { ... }

Wouldn't that lead to unpleasant surprises if someone later added a method medium {...} to this class or a subclass of it?

Seems like you'd need something like:

has _dbh       => ( builder => '_build_dbh' );
has shirt_size => ( default => 'medium'     );
@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 16, 2019

The idea seems to be that you should use default => method {'medium'} and any nonref would be a method name. But I feel like people are used to from Moo(se) that nonref defaults are literal and nonref builders are method names. Not to say we couldn't carve out a different strategy but it seems like something easy to slip up on.

@nrdvana

This comment has been minimized.

Copy link

nrdvana commented Oct 16, 2019

It's no surprise that you can't please everyone with a single design, but I feel the need to speak up in favor of the current perl5 object mechanism and in opposition to the slots idea.

I stand by my statements in An overview of the Perl 5 engine

Visibility

While other languages spend a bunch of effort on access rules between classes, Perl adopted a simple "if the name begins with underscore, don't touch it unless it's yours" convention. Although I can see how this could be a problem with an undisciplined software team, it has worked great in my experience. The only thing C++'s private keyword ever did for me was impair my debugging efforts, yet it felt dirty to make everything public. Perl removes my guilt.

Likewise, an object provides methods, but you can ignore them and just access the underlying Perl data structure. This is another huge boost for debugging.

I have exactly zero times in my career or hobby coding ever run into a problem caused by underscore variables being public. Meanwhile I have probably hundreds of cases per year where it was an advantage to debugging, because I could add a line that said use DDP; &p($some_object->_private_attr->some_other_object->{_one_of_its_fields}); and find out what had happened to my program without needing to subclass 3 objects that someone else wrote and somehow get someone else's module to create instances of my subclass just so I could reach the secret inner slots and print them out.

Doing stuff like this in production is taboo, of course, but sometimes it is much more valuable to get a system up an running in a few brief minutes of downtime than to build a correctly modeled solution to a problem in 3 hours of downtime. Developers need to take personal responsibility for their breaches of protocol, and that's all there is to it. I do not think it is a problem that needs a technological solution.

I like perl 5's "weak"/"loose" object system and it is the primary reason I don't switch to something like Python. It also lets me look back and laugh at all the suckers still using Java.

@aaronpriven

This comment has been minimized.

Copy link

aaronpriven commented Oct 16, 2019

I think it's a really good idea to start with a spec and then work on an implementation, and I agree with the general approach. I do have a few comments on this.

  • I agree that it makes a lot of sense for slots to be private by default, but I think making it harder to create public accessors in Cor than it is in existing modules like Moose and Moo will be frustrating to users and slow the adoption of Cor. Eventually someone will write Keyword::Cor::Haz and people will just write haz cheezburger is => 'ro' and it won't matter. Ultimately, I think it's unlikely that something like is => 'public' will be overused -- the problem in Moose isn't that t public accessors are too easy to create, but that it's too hard to create private ones.
  • I am not one of the people who loves punctuation, but I am convinced by the various arguments in favor of twigils, or something like that. If slots are all scalars, then it would be conceivable to just use a different sigil instead of $, although I don't know if there are any ASCII punctuation characters that are available to be used this way. I personally would prefer § but I realize that's difficult for some.
  • I'm a little unclear about builder slots. These are omitted from the "Summary of slot options" but are mentioned in the text above. If they exist, shouldn't they normally be lazy too?

Thanks.

@nrdvana

This comment has been minimized.

Copy link

nrdvana commented Oct 17, 2019

To clarify my previous post, I'm actually fine with the concept of a object-system-agnostic Slot metadata as Steven Little presented it at TPC. The part I object to is that there might be some new special language feature to enforce the access control to the storage of the slot. If it's anything other than simply accessing the field of the hashref, I'm never going to use it.

Meanwhile, I have a lot of other opinions on small things to change in Moo before it or something like it becomes an official core module. I've written them down in this gist

@yuki-kimoto

This comment has been minimized.

Copy link

yuki-kimoto commented Oct 17, 2019

Why many people think the object model of Raku and Moose is good for Perl 5 object system?

(Of course, Raku object system is good for Raku!)

Many changes is painful for Perl 5 and I like bless. It is easy and simple.

package Point;

sub new {
  my $class = shift;
  my $self = {x => 0, y => 0, @_};
  return bless $self, ref $class || $class;
}

sub x { shift->{x} }
sub y { shift->{y} }

I don't agree bless is old and bad part of Perl.

@blabos

This comment has been minimized.

Copy link

blabos commented Oct 17, 2019

class Point {
    has $.x; has $.y;
    method where { say "I am at ($.x, $.y)"; }
    method move_to_origin { $.x = 0; $.y = 0; }
}

First of all great job you are doing here :)

I'm still reading

Just non technical opinions:

  1. I like this form and I can easily see what $.attribute means just to look at it. This sounds beautiful to me. I really like it. I understand that sigils may not raising these feelings to everyone, but this sounds like pretty perl OO. We do not need to look like Java.
  2. I like and prefer sub foo {} instead of method foo {}
  3. I prefer class Foo is Bar instead of class Foo isa Bar
  4. abstract class or interface class?
@salva

This comment has been minimized.

Copy link

salva commented Oct 17, 2019

It may be a rehash of old conversations (though there's no history here on this proposal so hard to see what came before), but I wonder if you have considered the advantages of making slots look like lexical variables? In summary I'd like to propose a twigil-based notation using $.name:

How about just using $name, that would make has very similar to my, our and state.

 class Point {
    has ($x, $y);
    method x { $x }
    method y { $y }
}
@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 17, 2019

@salva: The argument in favour of a lexically-different "twigil" is largely that of readability. Consider in the following code:

my $x = 1;
our $y = 2;
class Foo {
    has $z;
    method m($w) {
        my $v = 5;
        return sum($v, $w, $x, $y, $z);
    }
}

It gets quite hard as a human reading the code to keep track of the fact that $w is a function parameter, $x and $y are captured outer lexicals, and $z is an instance variable. Try reading any sort of nontrivially-sized C++ or Java code and you'll see the problem. Member variables don't look distinct enough, and when reading the code you have to "just know" what member variable names exist. If they are notated slightly differently, it reminds you they behave differently.

    has $.z;
    method m($w) {
        my $v = 5;
        return sum($w, $x, $y, $.z);
    }

is just enough of a hint to think "ahah, this is a member variable, not a lexical."

I agree it's not a majorly important point; and one that can be addressed with a decent naming scheme - maybe all member vars are named $m_... or something - again that's often a convention in C++ code. As I point out above, we already have that problem with having to remember what are captured lexicals or package vars anyway, and that has always existed in Perl, so maybe it's not such a bad problem.

I just feel that, rather than being a cultural convention, the language could choose to enforce it if that was felt overall beneficial.

@salva

This comment has been minimized.

Copy link

salva commented Oct 17, 2019

@leonerd, in my experience, the problem with slot names in C++ and Java happens mostly on constructors and setters, where you commonly want to use the same name for the method argument and for the slot. For instance:

void setX(int x) {
    this.x = x;
}

In my opinion a Perl OO system should autogenerate those (even Java has popular libraries like Lombok, that do that) and otherwise, slots are just like any other variable, they have its scope and the programmer has to take that into account. I don't really see a problem with that.

What worries me more is that you will need to offer the programmer some way to access slots hidden by lexicals. For instance:

has $x;
method setX($x) {
    self->{x} = $x;
    # or
    $SELF::x = $x;
}

Also, how about accessing slots from parent classes? do you force the programmer to create accessors? what if I want something in the line of Java protected visibility?

has $SUPER::x;
parent_has $x;
@atoomic

This comment has been minimized.

Copy link

atoomic commented Oct 17, 2019

notes from a discussion

class Point {
   # `class` brings you all the fancy features for free
   # use strict; use warnings; use experemintal 'signature', ....; use v$];

    has $.x = -1; # default value (optional)
    has $.y = -1; # default value (optional)
    
    method y;               # provides the 'y' accessor/setter rw
    method get_y :ro($.y);  # provides one accessor 'get_y' for the 'y' slot

    method to_string {
        sprintf( "[%d, %d]" => $.x, $.y );
    }
}

my $point = Point->new( x => 3, y => 7 );
say $point->x;            # fatal error
say $point->to_string;    # [3, 7]
say $point->get_y;    # 7
Point->new( x => 4 ); # exception thrown because y is required
@HaraldJoerg

This comment has been minimized.

Copy link

HaraldJoerg commented Oct 17, 2019

I love it that a "native" OO system is now considered seriously for Perl! And I also like most of the syntax proposed here, and agree with "starting small". For most of the things discussed so far I don't have a strong opinion, I'll happily live with either one. There are just two things I'd like to comment on:

  • Not having an easy way to get accessor methods auto-generated is really inconvenient in some cases. I know that others have said this, so this is just another voice singing that tune. A significant part of my work with Perl was always "data munging" (converting from excel to database, from database to XML, ...). This gives lots of objects with lots of slots/attributes each, and the objects have not a single method beyond the accessors. Moose made writing those objects wonderfully easy, fast, and less error prone, and I'd really hate losing that. I'd gladly accept to load a module CorX::SemiAffordanceAccessor or whatever to get that again if it can't make it in core.
    By the way: I've a question in this context: Is it possible to call has in a loop, like in Moose, to create a batch of slots?
  • The abstract keyword can, in my opinion, safely be eliminated. If a class isn't intended to be instantiated, then a hint in the documentation will be a friendlier way to tell programmers about that.
@arc

This comment has been minimized.

Copy link

arc commented Oct 17, 2019

Some assorted comments.

I strongly dislike using string literals to name slots. There's apparently no way to access slots whose names don't match identifier syntax, so the additional quoting seems like a bad idea.

I firmly agree that slot access and method call should look different; they have sufficiently different semantics that I conflating the two is likely to lead to significant confusion.

One consequence of the approach taken by C++ and Java, where local syntax doesn't distinguish slot access from lexical-variable access, is that some programmers (and many style guides) insist on naming slots ("class members") in some distinctive way — typically m_foo or mFoo. I'd hate for a core Perl OO syntax to drive people in that direction.

I think attributes defaulting to what Moo/Moose call "bare" (no implicit accessor) is a fine idea. However, I suggest that making it easy to declaratively provide public accessors, at least for read-only access, is a sufficiently widely-desired feature that a core OO proposal should provide for it.

I also think that handles could be useful, especially if coupled with a mechanism akin to "native traits" (which, again, are just declarative sugar for trivial method definitions).

I'm concerned about changing method dispatch semantics (by using a different universal base class) for Cor-style instances. I think it may end up being confusing; it also risks slowing down the implementation of not only Cor method dispatch, but old-style method dispatch too. If at all possible I'd suggest keeping the semantics of method dispatch unchanged. I also dislike the idea that we'd have two completely incompatible object systems; that would prevent someone changing an existing class or role to use Cor, because doing so would break downstream inheritors/composers.

I'd really love it if we don't end up implementing a proposal that would make it impossible to, at some point, use some index-based internal representation, rather than hash refs.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 17, 2019

I've made a tiny proof-of-concept to demonstrate how simple it actually is in practice to provide some simple "looks like lexicals" member fields.

https://metacpan.org/pod/Object::Pad

The module has a somewhat odd name here, and it's prefixed with a big warning, so i'm not imagining anyone would actually use this in practice, but it might be a good ground to experiment with the basic concept and see what we make of it. A real core implementation could choose to spell the fields with a twigil, but for now i'm forced into what regular Perl lexicals can do.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 17, 2019

What worries me more is that you will need to offer the programmer some way to access slots hidden by lexicals. For instance:

has $x;
method setX($x) {
    self->{x} = $x;
    # or
    $SELF::x = $x;
}

@salva: Well that's definitely an argument in favour of a twigil then:

    has $.x;
    method setX($x) { $.x = $x; }
@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Oct 17, 2019

Agreed $. is much more Huffman friendly than self->.

@clscott

This comment has been minimized.

Copy link

clscott commented Oct 17, 2019

@leonerd

A real core implementation could choose to spell the fields with a twigil, but for now i'm forced into what regular Perl lexicals can do.

It IS possible to use twigils on CPAN, you may not want to jump through the necessary hoops though.

@clscott

This comment has been minimized.

Copy link

clscott commented Oct 17, 2019

@Ovid - thank you for volunteering to be the face of this process. As a perl Developer who started when perl 5.00 was released I have wanted this feature in perl for a long time.

Ovid, my points include questions - feel free to not answer them directly some are re-hashes of things others have said, I just wanted my turn at the brush.

My thoughts:

  1. @leonerd is correct re:issues with no-sigil slots - in particular how to distinguish whether self->x is a slot reference or a method call
  2. @latk is right about the number of new keywords
  3. Please don't make me boilerplate basic accessors. That's why I've used Moose,Moo,Class:::Accessor etc.
    If I want accessors it should be as simple as has x => ( rw => 1, accessor => 1); which should create FAST accessors for me. Best practices is Perl::Critic territory - not perl territory
  4. Backwards compatibility - I agree with @perigrin et al. Can we strive to break back compat and interop as little as possible? I am OK if interop is restricted to hash based objects
  5. Method Resolution Order - Can we make C3 the default MRO for an upcoming perl with the appropriate deprecation cycle? (I don't recall the p5p history on this one)
@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 17, 2019

Unfortunately global C3 MRO is probably impossible because it throws exceptions with particularly confusing inheritance trees, and these definitely exist. But that would be a good discussion for the p5p list.

@salva

This comment has been minimized.

Copy link

salva commented Oct 19, 2019

@leonerd, there is the issue of $. (aka $INPUT_LINE_NUMBER) having already a meaning in Perl.

For instance, "$.foo" is already valid Perl code which parses as $. . 'foo'. How do you plan to disambiguate between the two possibilities?

I reckon that such constructions are not very common but still we would be breaking backward compatibility.

@latk

This comment has been minimized.

Copy link

latk commented Oct 19, 2019

@salva Not an incompatibility because $.slot syntax would only be available within class/role blocks, and those do not currently exist. Adding these blocks is an opportunity to change or reserve additional syntax, which is why I'd recommend reserving additional syntax/keywords that might be interesting for a core object system, even if they wouldn't be used initially.

Even if it would break backcompat, this could be changed by a deprecation cycle and the use of feature flags. When slots are enabled, existing meaning of "$.foo" could be expressed as "${.}foo".

@trwyant

This comment has been minimized.

Copy link

trwyant commented Oct 19, 2019

FWIW, if $. changes meaning in general (as opposed to just in the scope of the new functionality), it breaks ack.

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 19, 2019

@salva That appears to be a syntax error here. $..foo would be valid but $.foo is not. (But $.x could be valid depending what follows since x is an operator)

@trwyant there's zero chance that the decision would be made to break $. regardless of what's chosen.

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 19, 2019

@leonerd, there is the issue of $. (aka $INPUT_LINE_NUMBER) having already a meaning in Perl.

For instance, "$.foo" is already valid Perl code which parses as $. . 'foo'. How do you plan to disambiguate between the two possibilities?

@salva: Really?

$ perl -Mstrict  -cE 'my $x = $.slot;'
Bareword found where operator expected at -e line 1, near "$.slot"
        (Missing operator before slot?)
syntax error at -e line 1, near "$.slot"
-e had compilation errors.

This isn't just a strictness problem:

$ perl -cE 'my $x = $.slot;'
Bareword found where operator expected at -e line 1, near "$.slot"
        (Missing operator before slot?)
syntax error at -e line 1, near "$.slot"
-e had compilation errors.

Now I will grant that under current interpolation rules:

$ perl -MO=Deparse -cE 'say "My name is $.name";'
use feature 'current_sub', 'bitwise', 'evalbytes', 'fc', 'postderef_qq', 'say', 'state', 'switch', 'unicode_strings', 'unicode_eval';
say "My name is $.name";
-e syntax OK

Which is just an interpolation that looks like say join $", 'My name is ', $., 'name';

However, as @latk points out, this new slot syntax would only be valid within class blocks anyway

@salva

This comment has been minimized.

Copy link

salva commented Oct 20, 2019

@leonerd, I was talking about the interpolation case exclusively. B::Concise shows the interpolation going on there:

$ perl -MO=Concise -cE 'say "$.foo";'
7  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v:%,us,{,fea=7 ->3
6     <@> say vK ->7
3        <0> pushmark s ->4
5        <+> multiconcat("foo",-1,3)[t3] sK/STRINGIFY ->6
-           <0> ex-pushmark s ->4
-           <1> ex-rv2sv sK/1 ->5
4              <#> gvsv[*.] s ->5
-e syntax OK

However, as @latk points out, this new slot syntax would only be valid within class blocks anyway

Perl has never been too fond of orthogonality, but well...

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 20, 2019

@salva: Ahyes, I'll accept that in the limited case of qq() interpolation, that is an annoying problem. I think in practice very few people are going to be wanting to interpolate $. within a quoted string without a whitespace there, so maybe it's acceptable.

As to it only being valid within class blocks - yes, that's largely the point. It can only possibly work within a method body, because that's the only time an instance exists in order to have the slots on it. It literally would not be meaningful to write

package main;
has $.message = "hello";
say $.message;

There's no instance, no place to store that message, outside of a method inside a class - so even if it did parse unambiguously it couldn't be implemented.

@salva

This comment has been minimized.

Copy link

salva commented Oct 20, 2019

Another expression that becomes ambiguous:

$ perl -MO=Concise -cE 'say "$$.foo";'
7  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v:%,us,{,fea=7 ->3
6     <@> say vK ->7
3        <0> pushmark s ->4
5        <+> multiconcat(".foo",-1,4)[t3] sK/STRINGIFY ->6
-           <0> ex-pushmark s ->4
-           <1> ex-rv2sv sK/1 ->5
4              <#> gvsv[*$] s ->5

And if you allow for array slots as in has @.a:


$ perl -MO=Concise -cE 'say "$@.foo";'
7  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v:%,us,{,fea=7 ->3
6     <@> say vK ->7
3        <0> pushmark s ->4
5        <+> multiconcat(".foo",-1,4)[t3] sK/STRINGIFY ->6
-           <0> ex-pushmark s ->4
-           <1> ex-rv2sv sK/1 ->5
4              <#> gvsv[*@] s ->5
@cxw42

This comment has been minimized.

Copy link

cxw42 commented Oct 21, 2019

Looks good!

  • Would you please consider moving this to a repo for the sake of threaded discussion? I am willing to help copy comments here into new issues.
  • I don't see does ROLE VERSION but may have missed it.
  • Ditto unit class a la Raku for file-scoped classes. I think the opener of a file-scoped class/role should look different from a forward declaration to avoid confusion.
  • I also wonder if VERSION could be expanded throughout the core to specify arbitrary version ranges, but that may be a separate topic :) .

Thanks to you and the contributors for your hard work!

@jjatria

This comment has been minimized.

Copy link

jjatria commented Oct 21, 2019

@cxw42 I think the class keyword is supposed to work like package, so both

class Foo;
...

and

class Foo {
    ...
};

would work.

@druud

This comment has been minimized.

Copy link

druud commented Oct 28, 2019

perl -Mstrict -cE 'my $x = $.slot;'

perl -Mstrict -cE 'my $x3 = $.x3'
-e syntax OK

@nrdvana

This comment has been minimized.

Copy link

nrdvana commented Oct 28, 2019

@druud I think that is parsed as $. x 3 right? Are you trying to emphasize that this will break existing code?

I think there's no real concern there, because if I'm following the conversation correctly, this:

method foo($bar) {
   $.x= $bar;
}

would internally compile as something like this:

sub foo {
  my ($self, $bar)= @_;
  use feature 'twigils';
  $.x= $bar;  # modifies $self->{x}
}

so the use feature 'twigils' compiler flag changes the parsing of variables so that alphanumerics following a '.' become part of the variable name. Only scopes with that feature enabled will be affected.

@aaronpriven

This comment has been minimized.

Copy link

aaronpriven commented Oct 28, 2019

I've been thinking about this. Lexical variables and global variables aren't distinguished by punctuation, and it seems to me that a slot is more like a different kind of scope than it is like a different kind of variable entirely. Once you write has $foo, perl already knows that $foo is a slot, just as perl already knows that $bar is a lexical once you write my $bar. The advantage is to the reader, and while I agree it's valuable to have the symbol for that purpose, it seems to me that people could always make up their own conventions -- $_slot, or $Slot, or $s_slot, or whatever works for them. Screwing around with perl's identifier parsing is bound to be complicated and there are bound to be edge cases (like the aforementioned $.x3, only other ones we haven't thought of yet) where meaning is ambiguous.

So while I don't really object to them, it seems to me twigils might be more trouble than they're worth.

@cxw42

This comment has been minimized.

Copy link

cxw42 commented Oct 28, 2019

I agree with @aaronpriven. I have been spending some quality time with toke.c recently, and it is truly gnarly!

As an example without twigils, I offer my own Sub::Multi::Tiny, which uses package variables to mimic named function parameters. I (of course ;) ) think it is perfectly readable.

Separately, how do I distinguish in spoken conversation between Cor and CORE? What about "Kern" ("core" in German) instead of "Cor"?

@druud

This comment has been minimized.

Copy link

druud commented Oct 29, 2019

@leonerd

This comment has been minimized.

Copy link

leonerd commented Oct 29, 2019

Indeed. This new syntax would only be meaningful inside a method function, and so it's really no great problem to just say that you can't interpolate certain weird forms of $. in there. It might have a tiny mental overlap for being slightly tricky to move code between those, you can't just copy-and-paste entirely without thinking. But then you couldn't do that from method to sub anyway because of no slot visibility.

In other thoughts: $:foo is just as clashy technically, but since it relates to formats ("The current set of characters after which a string may be broken to fill continuation fields in a format") I think even fewer people are likely to care about being able to interpolate it in modern code.

@notbenh

This comment has been minimized.

Copy link

notbenh commented Oct 29, 2019

I really like where this is going, thanks @Ovid, and everyone else involved. I'm most curious around how strict the version idea will be enforced. I just skimmed the comments so it's possible that this has been discussed and I missed it. It's also very possible that I'm making too much of the idea of version semantics proposed.

Given the way that the proposal is written, leveraging of version definitions, it implies that it will be more than just a helpful notation. Thus, is there an expectation that there will be a semantic way to restrict to known working cases? For example Role v2 can only be consumed by Class::A v3+ or Class::B v7+. Is the current thinking that this will be something left to the consumer to write on there own or are there going to be some notion of a semantic way to define this?

Also, on the topic of MI, is the expectation that multiple versions of Classes and Roles will live next to each other? If so then how would a specific version be requested? I don't even know how I would write it so here's a guess to illustrate but I hate this syntax:

my $Obj_1 = Some::Class[v1]->new(...); 
my $Obj_2 = Some::Class[v2]->new(...);

Lastly, if versions are indeed being promoted to actual leverageable meta data then I'm a little confused about the example for
class Dog isa Animal v2.0 { as this could be Dog of no version is a child of Animal v2.0 but it could also be read as Dog v2.0 is a child of Animal no version. Also what would it look like if I want to define a version of Dog that is a child of a specific version of Animal? Is that something currently being pondered?

@Grinnz

This comment has been minimized.

Copy link

Grinnz commented Oct 29, 2019

Also, on the topic of MI, is the expectation that multiple versions of Classes and Roles will live next to each other?

As long as a class or role is still a package name, this is not possible. The usual case and what I assume is being demonstrated here is a minimum version requirement, no more or less.

@nrdvana

This comment has been minimized.

Copy link

nrdvana commented Oct 30, 2019

@notbenh I would assume that class Foo v0.1 { ... } is shorthand for

package Foo;
our $VERSION= v0.1;

so just a shorthand for declaring the version. Since @Grinnz and I came to different conclusions, @Ovid should probably clarify that.

And on that note, this feature would require tooling updates because there are a lot of package analyzers and Dist::Zilla plugins that try munging the code based on variations of our $VERSION syntax and would need to be told how to interact with the new syntax.

@matthewpersico

This comment has been minimized.

Copy link

matthewpersico commented Nov 4, 2019

Seeing as it has been quiet, and that the conversation has veered off into the technical woods, I'd like to throw in this observation:

I am currently writing Python at my job and I really abhor the syntax for associative arrays. I keep thinking to myself: "If I was using Perl, I'd just use a hash with a sub hash with two elements and I'd just set $buildobs{$source} = { dpack_count => $dcount, binaries = {} } and then set each binary later as I get it with $buildobs{$source}->{binaries}->{$binaryName} = 1 and..." yada yada yada.

But then I start using said structure later on and boy the code is just littered with arrows and %{} derefs and all that noise Perl haters love to hate.

Wouldn't it have been easier to just create a class where I could have getters and setters? I could make the 'binaries' a 'UniqueSet' instead of faking it with a hash of values of 1. I could make cleaner looking method calls instead of line noise direct access.

I think that as we move along here, we should make sure that one criteria for the new classes is that the basic syntax for definition should be so simple that you would eventually create classes instead of naked hashes.

Just a thought.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.