On Thu, Mar 17, 2011 at 6:07 PM, Buddy Burden <[email protected]> wrote:
> Moosites,
>
> I'm working on adding type checking to Method::Signatures, and I want
> it to be able to handle roles. So, if I say:
>
> method foo (Blargy $bar)
>
> and Blargy is a role, I want it to verify that $bar is an object whose
> class composes the Blargy role. I believe that's as simple as
> checking that
>
> $bar->DOES('Blargy')
>
> right? But I've run into a couple of surprises, and I was hoping that
> the Moose masters here could shed some light. :)
>
> First thing is that I note that Moose::Util::TypeConstraint::Role
> doesn't actually do that. Rather, it does this:
>
> Moose::Util::does_role($_[0], $role)
>
> Is that different from just calling DOES?
>
> Second thing was that, since I have to account for the type being
> _either_ a class or a role (or a basic type such as Int, etc, but
> let's assume I already called find_or_parse_type_constraint earlier),
> I tried something like this:
>
> if
> (Moose::Util::TypeConstraint::find_type_constraint->("ClassName")->check($type))
> {
> $constr = Moose::Util::TypeConstraint::class_type($type);
> }
> elsif
> (Moose::Util::TypeConstraint::find_type_constraint->("RoleName")->check($type))
> {
> $constr = Moose::Util::TypeConstraint::role_type($type);
> }
>
> You can probably see right off that I have a bug there. I can't check
> to see if it's a classname first, because every role _is_ a class. I
> need to check for roles first. That wasn't the surprising part. The
> surprising part was that _it still works this way_. ANAICT, role_type
> never gets called, and all my role constraints end up being created by
> class_type, and when I dump them out they are indeed
> Moose::Meta::TypeConstraint::Class objects and not
> Moose::Meta::TypeConstraint::Role objects, but still when I get to the
> end and do:
>
> $constr->check($val);
>
> it passes. Now, I reversed the order anyway, just to make myself feel
> better (and to avoid running into some super-subtle bug one day that I
> might kill myself over not being able to track down), but I'm mightily
> curious as to why it actually works.
>
> My real problem, though, it that I want this to work with all possible
> roles, regardless of where they come from. Now, assuming I go back
> and invoke the proper Any::Moose incantations (which I have), then
> when Moose is loaded this works with Moose roles but not Mouse roles,
> and when Mouse is loaded it works with Mouse roles but not Moose
> roles, which I think _might_ be good enough. Or is it? I suppose
> there's always a chance that you could be including some modules which
> use Moose and some which use Mouse ... but that seems so unlikely that
> I'm not sure it's worth worrying about. I'd be interested in hearing
> what others think.
>
> And of course no matter which one I have loaded, it doesn't work with
> Role::Basic roles. Because the RoleName type constraint doesn't
> consider them to be roles, and, even if it did, it wouldn't DTRT (see
> first surprise). So I'll have to check for that separately (seems
> easy enough) and create my own type constraint for it, which I _think_
> should just do the DOES check. Something like so:
>
> type "RoleBasic$type" => where { $_->DOES($type) };
>
> Right? Or should I make them all subtypes of a common type (I can't
> see any advantage in that ATM, but perhaps I'm missing something)?
>
> Am I on the right track here, or does anyone smarter than I see any
> pitfalls? TIA!
>
>
> -- Buddy
>
Did anyone have any thoughts about this? My current code looks like this:
my %mutc;
# This is a helper function to initialize our %mutc variable.
sub _init_mutc
{
require Any::Moose;
Any::Moose->import('::Util::TypeConstraints');
no strict 'refs';
my $class = any_moose('::Util::TypeConstraints');
$mutc{class} = $class;
$mutc{findit} = \&{ $class . '::find_or_parse_type_constraint' };
$mutc{pull} = \&{ $class . '::find_type_constraint' };
$mutc{make_class} = \&{ $class . '::class_type' };
$mutc{make_role} = \&{ $class . '::role_type' };
$mutc{isa_class} = $mutc{pull}->("ClassName");
$mutc{isa_role} = $mutc{pull}->("RoleName");
}
# This is a helper function to find (or create) the constraint we need
for a given type. It would
# be called when the type is not found in our cache.
sub _make_constraint
{
my ($type) = @_;
_init_mutc() unless $mutc{class};
# Look for basic types (Int, Str, Bool, etc). This will also
create a new constraint for any
# parameterized types (e.g. ArrayRef[Int]) or any disjunctions
(e.g. Int|ScalarRef[Int]).
my $constr = eval { $mutc{findit}->($type) };
if ($@)
{
_type_error("the type $type is unrecognized (looks like it
doesn't parse correctly)");
}
return $constr if $constr;
# Check for roles. Note that you *must* check for roles before
you check for classes, because a
# role ISA class.
return $mutc{make_role}->($type) if $mutc{isa_role}->check($type);
# Now check for classes.
return $mutc{make_class}->($type) if $mutc{isa_class}->check($type);
_type_error("the type $type is unrecognized (perhaps you forgot to
load it?)");
}
So I'm thinking that AFA Role::Basic goes, I could just add something like this:
# Check for Role::Basic roles.
if ($type->isa('Role::Basic')
{
return $mutc{type}->( $type, { where => sub { $_->DOES($type) } } );
}
It would go just before the check against the RoleName constraint. It
would of course assume that $mutc{type} was defined just like others
in _init_mutc:
$mutc{type} = \&{ $class . '::type' };
Does that seem rational? I would especially love to hear from Ovid,
if he's following this discussion.
-- Buddy