Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Finding the interface implemented by an object

by oyse (Monk)
on Nov 27, 2007 at 21:02 UTC ( [id://653366]=perlquestion: print w/replies, xml ) Need Help??

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

As part of implementing a module that need to find out at runtime what methods an object supports I tested the following:

package TestPackage; use Data::Dump qw( dump ); my $obj = bless {}, __PACKAGE__; print 'Can dump()' if $obj->can( 'dump' ); print 'Can not_a_sub()' if $obj->can( 'not_a_sub' );

This prints "Can dump()" while in reality $obj does not implement dump(). Is there any other way to find out which methods an object implements, i.e. it's interface?

Update:

As usual I have given too little background to my question.

The reason I wanted to discover the interface of an object was that I was trying to implement yet another module for parameter validation for subroutines. Yes I know that there are more than enough of these already, but none of them works exactly I want them to and besides it was a challenge. Anyway, I wanted to be able to specify the type of a parameter, but instead of doing a isa() check on the supplied argument, it should check that it had the right functions. The check it self can be implemented via can(), but the module should be able to be given a package name and then determine what methods and object should be able to do. This is probably best illustrated with an example.

package A::Type; use Some::Module qw( a_func ); sub new { #standard constructor } sub a_method { } sub another_method { } package Another::Module; use Signature::Checker; sub func : Signature( A::Type parameter ) { }

The intention in this example is that the module Signature::Checker in some way adds parameter validation to subroutines that have the attribute Signature attached to them. When the Signature::Checker sees the type A::Type it should try to discover what methods A::Type implements. By using can() it will see the functions a_func(), new(), a_method() and another_method() which was not as intended.

As far as I understand Corion's answer this is not possible to implement in a general way. stvn's suggestion would work, but if I understand correctly, restrict the solutuion to only work with classes implemented with Moose. For me that would partly defeat the purpose of the module.

Replies are listed 'Best First'.
Re: Finding the interface implemented by an object
by stvn (Monsignor) on Nov 28, 2007 at 01:35 UTC

    You can use Class::MOP or it's offspring Moose to do this.

    First the Class::MOP version ...

    package TestPackage; use metaclass; # this sets up your class to use Class::MOP use Data::Dump qw( dump ); my $obj = bless {}, __PACKAGE__; print 'Can dump()' if $obj->meta->has_method('dump');
    This will not print "Can dump()" since Class::MOP knows that the method is not actually from your package, but has been imported from another package. To find the "interface" with Class::MOP, you have a few choices. First you can get all the locally defined methods* like this:
    my @methods = $obj->meta->get_method_list;
    This will return a list of method names suitable for doing things like $obj->meta->has_method($method_name) and $obj->meta->get_method($method_name) and other such introspection and reflection. Or, your second choice is to get all the methods this $obj supports, including inherited methods. In this case you do the following:
    my @methods = $obj->meta->compute_all_applicable_methods;
    The items in @methods this time are slightly different, you get a HASH reference which contains the method name (in the name key), the name of the class in which the method lives (in the class key) and a CODE reference for the actual method (in the code key).

    Now, you could also use Moose and Moose::Role to do similar things. Moose is basically syntactic sugar over the mechanisms that Class::MOP provides, so all the above examples apply here as well. With roles** and Moose::Role you can do the interface checking you are talking about. Here is an example:

    package MyFooInterface; use Moose::Role; requires 'bar';
    Then you apply the role to your class, which checks the role "interface" at compile time.
    package MyFoo; use Moose; with 'MyFooInterface'; sub bar { ... } # the bar method is required, the # class will fail to compile if it is # not present
    And you can also do your checking at runtime ...
    my $foo = MyFoo->new; if ($foo->does('MyFooInterface')) { ... }
    For more information, see the CPAN pages for Moose or Class::MOP, or for a quicker overview you can check out one of these talks recently given on the subject: And lastly, you can check out the two part article on Moose written by our very own merlyn.

    Footnotes:
    * Locally defined methods are those defined directly in your class, and not inherited.
    ** If you don't already know, a role is not unlike a mixin or the traits mentioned above, but with a little bit more of a Perl-ish twist. They are a core part of Perl 6 in fact.

    -stvn
      On the subject of Moose, I have a question ... Is multiple inheritance possible using Moose?

      I have tried some simple examples such as this:

      package Animal; use Moose; sub speak { my $self = shift; print 'A ', ref $self, ' goes ', $self->sound() . "\n"; } 1; package Danger; use Moose; sub speak { my $self = shift; print 'through an eye patch ...' . "\n"; } 1; package Mouse; use Moose; extends qw(Animal Danger); has 'sound' => (is => 'rw', default => 'squeek'); after 'speak' => sub { my $self = shift; print '[but you can barely hear it!]' . "\n"; }; 1; package main; my $danger_mouse = Mouse->new(); $danger_mouse->speak(); 1;

      The output I get is:

      A Mouse goes squeek [but you can barely hear it!]

      If I used Class::Std to implement this (using CUMULATIVE(BASE FIRST)) like so:

      package Animal; use Class::Std; { sub speak : CUMULATIVE(BASE FIRST) { my $self = shift; print 'A ', ref $self, ' goes ', $self->get_sound() . "\n"; } } 1; package Danger; use Class::Std; { sub speak : CUMULATIVE(BASE FIRST) { my $self = shift; print 'through an eye patch ...' . "\n"; } } 1; package Mouse; use Class::Std; use base qw(Animal Danger); { my %sound : ATTR( :name<sound>, :default<squeek> ); sub speak : CUMULATIVE(BASE FIRST) { my $self = shift; print '[but you can barely hear it!]' . "\n"; } } 1;

      I get the output I expect:

      A Mouse goes squeek through an eye patch ... [but you can barely hear it!]

      Is there a way to achieve similar results with Moose? I would just like to know.

      Thanks in advance =)

      UPDATE

      I knew I should have finished reading that post first =(

      This is achievable with Moose::Roles ... and a little thought. Like so:

      package Animal; use Moose; sub speak { my $self = shift; print 'A ', ref $self, ' goes ', $self->sound() . "\n"; } 1; package Danger; use Moose::Role; requires 'speak'; after 'speak' => sub { my $self = shift; print 'through an eye patch ...' . "\n"; }; 1; package Mouse; use Moose; extends 'Animal'; with 'Danger'; has 'sound' => (is => 'rw', default => 'squeek'); after 'speak' => sub { my $self = shift; print '[but you can barely hear it!]' . "\n"; }; 1; package main; my $danger_mouse = Mouse->new(); $danger_mouse->speak(); 1;

      This gives the same output as the Class::Std version. My apologies all for jumping the gun.


      Smoothie, smoothie, hundre prosent naturlig!

        There are two important things to note about the Moose solution, which make it superior to the Class::Std solution.

        1. A Mouse is not a Danger
        2. Inheritance is an "is-a" relationship, and the Class::Std version incorrectly models the Mouse's relationship with Danger. The Moose version states that a Mouse "does" Danger, which still doesn't read quite right, but Danger is clearly more of a Trait of this particular Mouse, and not something that the Mouse "is".

        3. The CUMULATIVE(BASE FIRST) approach lacks a degree of control
        4. Because CUMULATIVE(BASE FIRST) determines the order in which your methods are called, you are limited to how much control you can have. Using the Moose modifiers before/after/around you have a great degree of control over when and how your methods get called. Moose also provides augment/inner which only works with classes (it makes no sense for roles), but also provides a more flexible and powerful means of controlling dispatch. See the Moose::Cookbook::Recipe7 for a good example of this.

        It should also be noted (with all due respect to TheDamian) that that Class::Std has 56 outstanding Bugs some over 2 years old and many which are quite serious. It also seems to be no longer maintained (last upload was Feb. 2006). In contrast, Moose is very actively developed by a handful of developers and is being used heavily in several large production sites so bugs tend to get fixed rather quickly.

        -stvn
Re: Finding the interface implemented by an object
by Corion (Patriarch) on Nov 27, 2007 at 21:09 UTC

    Actually, Perl is correct. $obj->dump() would "work" in your case. There is no difference between a subroutine in a package and a method of an object blessed into said package, at least as far as Perl is concerned. The arrow-call syntax $obj->method(@args) is little more than syntactic sugar around TestPackage::method($obj, @args).

    There is no real concept of an interface in Perl other than the documentation of methods of a class/package.

Re: Finding the interface implemented by an object
by shmem (Chancellor) on Nov 27, 2007 at 23:14 UTC
    Is there any other way to find out which methods an object implements, i.e. it's interface?

    Since there's no notion of 'interface' in perl, you would need to implement a method $obj->interface,or some such, in a class. Walking the symbol table doesn't lead to reliable results either, since inspecting the CODE slots of typeglobs doesn't tell you which package those code refs were compiled into. See What CODE typeglob slot is my anonymous sub in? for details. (There's not much difference between anonymous coderefs and those stored in a glob besides the fact that the latter are stored in a typeglob slot.)

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Finding the interface implemented by an object
by RaduH (Scribe) on Nov 27, 2007 at 21:13 UTC
    You may need to use traits to have access to a mechanism similar to Java's reflexion. See here for more details.

    Hope this helps!

Re: Finding the interface implemented by an object
by aquarium (Curate) on Nov 27, 2007 at 22:29 UTC
    walk through the symbol table instead
    the hardest line to type correctly is: stty erase ^H

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://653366]
Approved by Corion
Front-paged by naikonta
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (6)
As of 2024-04-25 10:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found