Ovid has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on a large project with heavy refactoring. Sometimes I am finding myself in the awkward position of refactoring classes in such a way that when I pass objects in argument lists, I am not interested in the class type, but in the class capabilities (similar to Ruby's "duck typing"). In short, I want this:

sub foo { my ($self, $object_or_class) = @_; die $message unless does($object_or_class, @methods); ... }

This is becoming more of a problem as I find a large class which needs to be factored into two or more classes. The does() function is trivial to write (as long as one appreciates the caveats involved), but I was looking to see if there is something on the CPAN which already handles this. I see nothing, but if someone else does, I'd like to hear about it.

First pass of the does() function:

use Scalar::Util qw/blessed/; sub does { my ($proto, @methods) = @_; return if ref $proto && ! blessed $proto; my @coderefs; foreach my $method (@methods) { push @coderefs, $proto->can($method) || (); } return wantarray ? @coderefs : \@coderefs; }

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Class capabilities
by diotalevi (Canon) on Oct 11, 2005 at 21:19 UTC

    use Params::Validate;

    sub foo { my ($self, $object_or_class) = validate_pos( @_, 1, # or strictly, { isa => __PACKAGE__, type => +OBJECT }, { can => \ @methods } ); .... }
Re: Class capabilities
by moot (Chaplain) on Oct 11, 2005 at 21:10 UTC
    I'm not sure if there's a CPAN module for 'does', but your first cut will allow the foo() method to continue if at least one method is available, which may not be what you want. Also in scalar context an array ref will be returned, which will always evaluate to true, so even if no @methods pass the test, foo() will continue.

      Yeah, my first cut was a bit too simple. It's now:

      sub does { my ($proto, @methods) = @_; return if ref $proto && ! blessed $proto; my @does; foreach my $method (@methods) { push @does, $proto->can($method) || (); } @does = () unless @does == @methods; return wantarray ? @does : @does ? \@does : (); }

      Thanks!

      Cheers,
      Ovid

      New address of my CGI Course.

        Kind of complicated. Why not return as soon as you fail to find any method?

        sub does { my ( $proto, @method ) = @_; return if ref $proto && !blessed $proto; my @does; push @does, $proto->can( $_ ) || return foreach @method; return wantarray ? @does : \@does; }

        That said I think diotalevi’s Params::Validate recommendation is spot on.

        Makeshifts last the longest.

Re: Class capabilities
by Zaxo (Archbishop) on Oct 11, 2005 at 23:03 UTC

    A does method sounds like a boolean to me. I'd look for it to return true if and only if the object can do all the listed methods. Also, your error return will not occur if $proto is not a reference.

    sub does { my ($proto, @methods) = @_; return if !ref $proto || !blessed $proto; # DeMorgan my @coderefs = map {$proto->can($_) || ()} @methods; return @coderefs == @methods; }

    After Compline,
    Zaxo

Re: Class capabilities
by dragonchild (Archbishop) on Oct 12, 2005 at 02:01 UTC
    It doesn't sound like you're not duck-typing so much as looking for interfaces (or P6 roles). You're not looking for can() so much as provides (from Java) or does (from P6 et al). can() is too fragile.

    Without the OO Metamodel that stvn is writing, there's no simple way that I know of in P5 to publish that you provide a given interface. Solving this in a perlish way would be a neat thing to do.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      You do raise some good points. I do think the Params::Validate suggestion is the best so far (I always forget about that module), but I do sort of like the idea of a module published which states explicitly that it depends on the can() method properly functioning (meaning that if AUTOLOAD or something similar is being used that can should be overridden). Too often we let ourselves get sloppy and forget little details like that.

      Cheers,
      Ovid

      New address of my CGI Course.

        A quick CPAN search for "interface" brings up the interface module. That looks promising. It references Class::Contract, which may be a little too much koolaid for you, but it would definitely do what you're looking for. :-)

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Class capabilities
by adrianh (Chancellor) on Oct 12, 2005 at 09:03 UTC
    I was looking to see if there is something on the CPAN which already handles this. I see nothing, but if someone else does, I'd like to hear about it.

    Refactor to roles and use Class::Roles does method?

Re: Class capabilities
by sgifford (Prior) on Oct 12, 2005 at 15:44 UTC
    Have you considered using the can function from UNIVERSAL?

    Update: As Ovid says, UNIVERSAL::can shouldn't be used directly, but as an object method. I included the link only to show where to find the documentation.

      UNIVERSAL::can should not be used directly. If a class overrides can (which they probably should if they use AUTOLOAD), UNIVERSAL::can will return incorrect results.

      Cheers,
      Ovid

      New address of my CGI Course.