Sometimes, even the simplest of modules prove the most handy.
Have you ever been in the situation where you're digging through someone else's code and they call a method that you've never heard of before? It's not in the code you're using - it's inherited somehow - but what class does the method come from? And how do we work this out?
Inheritance is complicated in Perl - unlike many other languages it allows you to have multiple inheritance, meaning that you can get methods from one or more classes, and deciding which method is called in preference to which method.
All we really need is way of getting what a module inherits from, what those modules inherit from, and what modules those modules inherit from, and so on, in a big list so we can tell what module's method gets called first. And this is what Class::ISA does
The usual way that you declare a base class in Perl is to use
the use base
directive.
package Flintstone::Fred; use base qw(Flintstone Person);
This declares that Flintstone::Fred is a class that inherits from two base classes ('superclasses') Person and Flintstone - unlike languages like Java in Perl you can inherit from as many classes as you want. Perl looks though each of it's superclasses in order to find subroutines that implement methods and it uses the first it finds. This means that if you call a method on Flinstone::Fred it'll look in the subroutine in Flintsone::Fred, then in Flintstone and then in Person.
The situation gets more complicated if Flintstone is itself a subclass of another superclass (or several superclasses) itself. Perl uses a depth first search algorithm which means that it'll look in all of Flintstone's superclasses for subroutines (and their superclasses and so on) before looking in Person's
Like most people, at this point my head is beginning to spin. What we need is a diagram. Luckily, the Graphviz extension Graphviz::ISA::Multi is able to create nice graphs for us.
#!/usr/bin/perl
# turn on perl's safety features use strict; use warnings;
# get the name of the class my $class = shift or die "usage: $0 <classname>\n";
# create the graph use GraphViz::ISA::Multi; my $graph = GraphViz::ISA::Multi->new(); $graph->add($class);
# open the file my $filename = $class; $filename =~ s/::/-/g; use IO::File; my $fh = IO::File->new("> $filename.png") or die "Can't open '$filename.png': $!";
# write it out as a png print $fh $graph->as_png;
So for our module, we get this nice graph like so:
Class::ISA is ridicously easy to use. Although it doesn't export
subroutines, all you have to do to work out a module's superclass is
directly is call the Class::ISA::super_path
subroutine explicitly
with the name of the module.
#!/usr/bin/perl
# turn on the safety features use strict; use warnings;
# load the modules use Class::ISA; use Flintstone::Fred;
# print out the module search order foreach my $class (Class::ISA::super_path('Flintstone::Fred')) { print "$class\n" }
This prints out
Flintstone Cartoon Entity Person
Note that Entity is only listed once in the list, the first time it's inherited from by Cartoon. Obviously if the methods weren't in it the first time, they won't be in it the second! There's no need for it to be kept in the method search list.
So let's move onto the problem of finding out methods. What we have
to do is search though each of the classes looking for the method
subroutine. We need to use (Class::ISA::self_and_super_path
because we should obviously start looking in the bottom class itself
for the method before checking it's superclasses. We also need to
take care to add the UNIVERSAL
class onto the list, as all classes
'inherit' from this class if they specify it or not if the class
search list is exhausted.
#!/usr/bin/perl
# turn on the safety features use strict; use warnings;
# load the support modules use Class::ISA;
# check the arguments unless (@ARGV == 2) { die "usage: $0 <modulename> <methodname>\n"; }
# work out what the module we're loading and load it my $module = shift; eval "use $module"; if ($@) { die "Can't load module '$module': $@" }
# get the methodname my $methodname = shift;
# look through each of the routines for our method my @classes = (Class::ISA::self_and_super_path($module), "UNIVERSAL"); # first look for the method foreach my $class (@classes) { if (defined(&{"${class}::${methodname}"})) { print "${module}->${methodname} = ${class}::${methodname}\n"; exit 0; } }
# failing that look for an AUTOLOAD foreach my $class (@classes) { if (defined(&{"${class}::AUTOLOAD"})) { print "${module}->${methodname} = ${class}::AUTOLOAD\n"; exit 0; } }
# give up print "${module}->${methodname} not found\n"; exit 1;
And it works. We have a nice simple tool we can use from the command line now:
bash-2.05b$ ./find_method Flintstone::Fred name Flintstone::Fred->name = Flintstone::name
bash-2.05b$ ./find_method Flintstone::Fred wibble Flintstone::Fred->wibble = Person::AUTOLOAD