Perl-Apollo / Corinna

Corinna - Bring Modern OO to the Core of Perl
Artistic License 2.0
156 stars 19 forks source link

Syntax for consuming roles #49

Closed Ovid closed 2 years ago

Ovid commented 2 years ago

Edit: we are probably not going to provide any of this for the MVP. Still a good place to hear other's thoughts, though.

The MVP for Corinna provides roles.

class Foo :does(Bar) {
    ...
}

However, they're very bare-bones right now.

Exclusion/Aliasing

However, we don't have method exclusion or aliasing (I wouldn't mind a third option being a "rename" to combine the common case of excluding and aliasing). The only reason this was not added to the MVP is because we don't have a suitable candidate syntax. Suggestions very welcome here.

For the time being, the very ugly workaround if you need to exclude a method is to consume it in a base class and then override it in your actual class. This is the type of infrastructure code that is there only to overcome a limitation. This needs to be fixed.

Mixins

In Moo/se, you can consume multiple roles all at once or individually:

with qw(
    Role1
    Role2
);

# versus

with 'Role1';
with 'Role2';

The first form you get compositional safety. If both roles provide a name method, you get a conflict and you need to exlude one or both (and possibly alias them if you still need the behavior. See "Exclusion/Aliasing" above.

The first form is very much recommended. However, the second form is useful if you need to treat the roles as mixins. If there are method conflicts, the last one wins.

My proposal would be this:

class Foo :does(Role1 Role2) { ... } # conflict resolution
class Foo :does(Role1) :does(Role2) { ... } # mixins

Both cases would need to handle the "Exclusion/Aliasing" issue.

Runtime Role Application

We currently have nothing described for this and not everyone is clear on why you would want it.

Let's say that you have to roles: Role::JSON and Role::XML. They each provide the same methods and have the same requirements. A client connects to your server and by default, you reply with JSON. However, they send the following in their headers:

Accept: application/xml;

So you would apply the Role::XML at runtime and that would override the Role::JSON methods. I haven't tested this, but I think the following would work for Object::Pad:

my $metaclass = Object::Pad::MOP::Class->for_class( $class )
$metaclass->add_role('Role::XML');

That works, though it seems a touch clumsy. Is this feature useful enough that we should provide sugar?

shadowcat-mst commented 2 years ago

Exclusion/aliasing is a highly discouraged code smell in Moose and was intentionally left out of Moo/Role::Tiny as a result.

We've never needed to add it to Moo - after over a decade - so arguments that "but we need it sometimes to do the bad thing" are unconvincing based on the fact that apparently people don't need it that badly in practice after all.

I suggest instead we leave it out of the design entirely for the moment.

Grinnz commented 2 years ago

The primary use case for runtime role application is to add functionality to an existing class without globally modifying the class - as runtime role application creates a new subclass. I have rarely also used it to modify existing behavior with method modifiers.

The example design would make more sense as a class where either Role::XML or Role::JSON is applied to implement its behavior, not where one starts out applied.

Ovid commented 2 years ago

For now, as it's agreed it would be out of scope for v1, I'm closing this ticket. When Corinna is finally released, we can reconsider.