Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to get all valid methods for a particular Perl class?

Is it possible to get all valid methods for a particular Perl class?

I am trying to manipulate the symbol table of a class and get all of its methods. I found I can separate out the subroutines from the non-subroutines via the $obj->can($method), but that doesn't do exactly what I think it does.

The following returns:

subroutine, Property, croak, Group, confess, carp, File

However, subroutine isn't a method, (just a subroutine), and croak, confess, and carp were all imported into my package.

What I really want to print out is:

Property,Group, File

But I'll take:

subroutine, Property,Group, File

Below is my program:

#! /usr/bin/env perl

use strict;
use warnings;
use feature qw(say);

my $sections = Section_group->new;
say join ", ", $sections->Sections;

package Section_group;
use Carp;

sub new     {
    return bless {}, shift;
}

sub Add {
    my $self                = shift;
    my $section             = shift;
}

sub Sections {
    my $self                = shift;

    my @sections;
    for my $symbol ( keys %Section_group:: ) {
        next if $symbol eq "new";   # This is a constructor
        next if $symbol eq "Add";   # Not interested in this method
        next if $symbol eq "Sections";      # This is it's own method
        push @sections, $symbol if $self->can($symbol);
    }

    return wantarray ? @sections : \@sections;
}

sub subroutine {
    my $param1              = shift;
    my $param2              = shift;
}

sub Group {
    my $self                = shift;
    my $section             = shift;
}

sub File {
    my $self                = shift;
    my $section             = shift;
}

sub Property {
    my $self                = shift;
    my $section             = shift;
}
like image 810
David W. Avatar asked Aug 22 '13 20:08

David W.


2 Answers

This is fairly trivial. We only want to keep those sub names that were originally defined in our package. Every CV (code value) has a pointer to the package where it was defined. Thanks to B, we can examine that:

use B ();

...

if (my $coderef = $self->can($symbol)) {
  my $cv = B::svref_2object $coderef;
  push @sections, $symbol if $cv->STASH->NAME eq __PACKAGE__;
}

# Output as wanted

That is, we perform introspection using svref_2object. This returns a Perl object representing an internal perl data structure.

If we look into a coderef, we get a B::CV object, which represents the internal CV. The STASH field in a CV points to the Stash where it was defined. As you know, a Stash is just a special hash (internally represented as a HV), so $cv->STASH returns a B::HV. The NAME field of a HV contains the fully qualified package name of the Stash if the HV is a Stash, and not a regular hash.

Now we have all the info we need, and can compare the wanted package name to the name of the stash of the coderef.

Of course, this is simplified, and you will want to recurse through @ISA for general classes.


Nobody likes polluted namespaces. Thankfully, there are modules that remove foreign symbols from the Stash, e.g. namespace::clean. This is no problem when the CVs of all subs you are calling are known at compile time.

like image 65
amon Avatar answered Nov 15 '22 06:11

amon


What are you trying to do? Why does it matter how a class defined or implements a method it responds to?

Perl is a dynamic language, so that means that methods don't have to exist at all. With AUTOLOAD, a method might be perfectly fine and callable, but never show up in the symbol table. A good interface would make can work in those cases, but there might be cases where a class or an object decides to respond to that with false.

The Package::Stash module can help you find defined subroutines in a particular namespace, but as you say, they might not be defined in the same file. The methods in a class might come from an inherited class. If you care about where they come from, you're probably doing it wrong.

like image 31
brian d foy Avatar answered Nov 15 '22 06:11

brian d foy