On Thu, Mar 17, 2011 at 6:07 PM, Buddy Burden <barefootco...@gmail.com> 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

Reply via email to