Of course you can `requires` attributes!
During the patch -p2 hackathon, two seasoned Perl programmers told me that
"you can't requires
attributes". This sounded weird to me, as attributes are exposed as methods and besides, I'm doing that all the time in my current work project.
Let's look at the simplest possible example:
package MyRole;
use Moo::Role;
requires 'my_attr';
package MyClass;
use Moo;
with 'MyRole';
has my_attr => ( is => 'ro' );
And then run it:
`Can't apply MyRole to MyClass - missing my_attr at /home/book/perl5/lib/perl5/Role/Tiny.pm line 317.`.
Indeed, that looks like it doesn't work.
Let's consider what the code does for a moment:
requires
checks if a subroutinemy_attr
exists in theMyClass
namespacehas 'my_attr'
adds themy_attr
subroutine (an accessor) in theMyClass
namespace
Why doesn't this work? First, we should keep in mind that our beloved Perl has a complex organisation of compile-time and runtime phases. Although Moo
(and Moose
) code is declarative, most of it is nonetheless executed during a runtime phase. Second, we can fix this without looking at the code of any of Moo
, Moo::Role
or Role::Tiny
. Simply use our knowledge of Perl, and realize what is happening.
Calling with
is asking the roles to check (among other things) if the methods they require actually exist in the calling class. But this line of code is executed before the has
, which actually inserts my_attr
in the class namespace. So my_attr
doesn't exist in MyClass
at the time with
is called.
The fix is simple enough. Let's simply swap the with
and has
lines:
package MyRole;
use Moo::Role;
requires 'my_attr';
package MyClass;
use Moo;
has my_attr => ( is => 'ro' );
with 'MyRole';
Which compiles (and runs) just fine.
I think my with
declarations are going to move to the bottom of my files as I design more of my code around roles.
It works in Moo, but there are some bugs with
requires
and attributes in Moose.Demonstration...
A cleaner way (in my opinion, yours may differ), is to use forward declarations. The promise a subroutine will be there, even if it doesn't exist at compile time. That means you can put your with statement wherever you like. I've encountered cases with nested roles where even a trailing with doesn't solve the problem.
I noticed the issue and I trusted in developers desgin, so I supposed the right way was to put attributes in the MyRole package.
If an attribute needs some method in its builder or default sub, it can be added in the requires list. For example you can require the builder sub.
Sorry for replying a few month later, but your code example gives an error:
The only way I've found to avoid this without putting the
with
at end is the following: