Re: Type checking for roles

[email protected] (Buddy Burden)
Newsgroups perl.moose
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.