9
votes

Imagine a series of complex grammars represented as roles, although this simple example is enough to show the conflict:

role Alpha {
    token alpha { :i <[A..Z]> }
    }

role Digit {
    token digit { <[0..9]> }
    }

role Either
    does Alpha
    does Digit {
    token either { <alpha> | <digit> }
    }

grammar Thingy
    does Either
    does Alpha
    {
    token TOP { <alpha> <either>* }
    }

my $match = Thingy.parse( '1a3' );
dd $match;

This doesn't work because Perl 6 doesn't untangle the relationships to figure out that the conflict is actually the same thing from the same source:

Method 'alpha' must be resolved by class Thingy because it exists in multiple roles

But, reading S14, I see:

A role may not inherit from a class, but may be composed of other roles. However, this "crony" composition is not evaluated until class composition time. This means that if two roles bring in the same crony, there's no conflict--it's just as if the class pulled in the crony role itself and the respective roles didn't. A role may never conflict with itself regardless of its method of incorporation.

I read that to mean that the roles are applied as late as possible, so the class Thingy would be able to disentangle that Alpha is included in two different parts. I figured this would work something like creating a list of all of the roles that would make up the final class, then applying that list only to the final class. That way, something like Either would mix-in only the things it defined and would rely on the later composition to bring in Alpha.

I ran across this when I was trying to implement grammars for various (IETF) RFCs. Many of them reference the grammars from other RFCs, which makes it impossible for Perl 6 to resolve inheritance by C3. So, I figured that roles would disconnect the relationships. Apparently it doesn't.

1
The answer to the question in title is "Yes, it should."Sinan Ünür
@ugexe Your example is not the same. That's two different roles supplying different methods. In my example, only one role supplies the method.brian d foy
@briandfoy: FWIW, I read that S14 quote the same way and hope that one of the Perl 6 core developers responds to this.smls
Golf'd: role A { method m {} }; role B does A {}; class C does A does B {}.raiph
This may have been fixed at the end of 2019: blogs.perl.org/users/vadim_belman/2019/12/post.htmlbrian d foy

1 Answers

3
votes

Yes, Perl 6 should be able to untangle the inclusion of the same role from different sources.

Simple definitions of a role are:

A role encapsulates some piece of behavior or state that can be shared between classes.

or

A role is a name for a discrete collection of behaviors.

So, let's say we have a Floatable behavior for objects that can be floated on water, and a Sailable behavior for objects that can be sailed. Naturally, objects that can be sailed can be floated. A Sloop is naturally floatable and sailable. There is no conflict in the fact that the same Floatable behavior is conveyed by both Floatable and Sailable roles.

In Perl, this works as expected (it works with Moose as well):

#!/usr/bin/env perl

use v5.24; # why not
use warnings;

package Floatable {
    use Moo::Role;
    sub float { say "float" }
}

package Sailable {
    use Moo::Role;
    with 'Floatable';
    sub sail { $_[0]->float; say "sail" };
}

package Sloop {
    use Moo;
    with qw( Floatable Sailable );
}

my $s = Sloop->new;

$s->sail;

This behavior is the intuitively obvious one.

One problem I noticed when looking at the Perl6 documentation for roles is the lack of a simple, one sentence definition:

Roles are in some ways similar to classes, in that they are a collection of attributes and methods. They differ in that roles are also meant for describing only parts of an object's behavior and in how roles are applied to classes. Or to phrase it differently, classes are meant for managing objects and roles are meant for managing behavior and code reuse.

...

Role application differs significantly from class inheritance. When a role is applied to a class, the methods of that role are copied into the class. If multiple roles are applied to the same class, conflicts (e.g. attributes or non-multi methods of the same name) cause a compile-time error, which can be solved by providing a method of the same name in the class.

Apparently, when perl6 encounters two roles that provide the exact same behavior, it thinks of this as a conflict in a way I find unreasonable.

Note the subtle distinction in the following example:

#!/usr/bin/env perl

use v5.24; # why not
use warnings;

package Floatable {
    use Moo::Role;
    sub float { say "float" }
}

package Sailable {
    use Moo::Role;
    sub float { say "floating bonds to finance journey "}
    sub sail { $_[0]->float; say "sail" };
}

package Sloop {
    use Moo;
    with qw( Floatable Sailable );
}

my $s = Sloop->new;

$s->sail;

In this case, the conflict is to be expected, because two distinct roles want to claim to provide the same behaviors.