Stephen Quinney <step...@jadevine.org.uk> wrote:
>Stephen Quinney <step...@jadevine.org.uk> wrote:
>> {
>>     package SJQ::Role::Foo;
>>     use Moo::Role;
>>     use MooX::HandlesVia;
>> }
>>
>> {
>>     package SJQ::Role::Bar;
>>     use Moo::Role;
>>     use MooX::HandlesVia;
>> }
>>
>> {
>>     package SJQ::Baz;
>>     use Moo;
>>
>>     with 'SJQ::Role::Foo','SJQ::Role::Bar';
>>
>>     use namespace::clean;
>> }

Hi, Stephen. What's happening here is that MooX::HandlesVia looks for
the "has" routine in the package importing it (normally the one
provided by Moo or Moo::Role), wraps it with a version that
understands "handles_via", and replaces "has" in the importing package
with the wrapped version. Then Moo::Role treats the wrapped "has" as a
method implemented by the role, rather than as a declarator to be
ignored; so the wrapped "has" implementations in each role are treated
as conflicting methods, with the conflict to be resolved by the class
that composes them.

I think this behaviour is as documented:

https://metacpan.org/pod/Moo::Role#CLEANING-UP-IMPORTS
"Moo::Role cleans up its own imported methods and any imports declared
before the use Moo::Role statement automatically. Anything imported
after use Moo::Role will be composed into consuming packages."

That is: since the wrapped "has" in each role isn't one of Moo::Role's
own imported methods, and it's (necessarily) added after Moo::Role is
imported, and it isn't cleaned up in any other way, Moo::Role is
behaving as documented.

I suspect the simplest workaround is to add "use namespace::clean" to
each role; this will remove the wrapped "has" routines.

> To partially answer my own question, it appears that if I use "with" once
> for each role it works fine, for example:
>
> {
>     package SJQ::Baz;
>     use Moo;
>
>     with 'SJQ::Role::Foo';
>     with 'SJQ::Role::Bar';
>
>     use namespace::clean;
> }

That means something slightly different, which is why it doesn't yield
the exception you've seen. Using one "with" for several roles composes
the roles simultaneously-ish, specifically so that method conflicts
are detected. Using multiple "with" calls composes them serially, and
therefore does conflict detection serially, too. So the "has" in your
::Foo gets installed as a method in ::Baz first; then the "has" in
::Bar is ignored, because Foo's "has" is treated as a method that the
class has implemented (which is one way to resolve a genuine role
method conflict).

A good rule of thumb is to use serial role composition only when you
know exactly why you're doing that (which is normally going to be to
resolve a method conflict in favour of the implementation provided by
one of the roles). In this case, my guess is that you're going to be
better off fixing the spurious conflict, rather than papering over it.

-- 
Aaron Crane ** http://aaroncrane.co.uk/

Reply via email to