Write a script to list methods of a package/class.
package Calc;
use strict;
use warnings;
sub new { bless {}, shift; }
sub add { }
sub mul { }
sub div { }
1;
BEGIN
mul
div
new
add
Well, this looks pretty simple. The package in the example has four methods, and the example output list all fo... Wait! It has five lines of output.
What is that BEGIN
doing there? There's no method named BEGIN
in Calc
. And Calc -> can ("BEGIN")
is not going to return
a reference to it.
Now, it's true that if you define a subroutine named BEGIN
,
then that subroutine gets executed as BEGIN
time; but that
does not mean the package contains a method BEGIN
in the
absence of such a routine.
Perhaps the make of the challenge has a fundamental different
view of what a method is than we have. And we don't understand
at all why BEGIN
gets this special treatment, but not INIT
,
CHECK
, UNITCHECK
, nor END
.
It's only not clear what should happen in the presence of an
AUTOLOAD
method. It can be argued that if a class has an
AUTOLOAD
method, it has any possible method. We decide to
not follow that road, and just not list any methods which would
trigger the AUTOLOAD
method (except the AUTOLOAD
method itself).
A further note, we're going to ignore any lexical subroutines. Lexical subroutines are bound to their lexical scope (and hence, found it the scope's lexical pad), not so much to the package.
We also ignore any anonymous subroutines.
This is a Perl specific challenge, so we're only presenting
a solution in Perl. We take the class name as argument, use
the given class name, and search for methods right after.
This assumes that the package is found in file with the same
name (after substituting ::
with /
, and prepending .pm
).
This is not required in Perl, but it's quite common. Of course,
the module must be found in @INC
. Here's the code to get
the module from the command line, and load it:
my $module = shift;
eval "use $module; 1" or die "Failed to load $module.pm: $@";
After loading, we're going to inspect the symbol table. The
symbol table can be accessed as a hash — with a special name:
the package name followed by a double colon. So, the symbol
table for the package Calc
is the hash %Calc::
.
The keys of the symbol tables are the symbols (duh!) found in
the package. The symbols are the names of the sub routines,
package variables (sans sigils), file handles, globs, formats,
and other named tokens in the package.
Note that different variables can share the same slot:
@foo
, $foo
, sub foo
all use the symbol foo
.
The values in the symbol tables are
typeglobs
with information about the symbols. In particular, this typeglob contains
entries (slots) indicating what the symbol is used for:
SCALAR
, ARRAY
, HASH
, CODE
, IO
, FORMAT
, GLOB
if the symbol is used as a scalar,
array, hash, code reference, file or directory handle, format
or glob. Multiple slots can be present, if the symbol is used
for different things.
The slot which is of interest for our solution is the CODE
slot.
A method is present if the package, if and only if the slot in
the symbol table contains a CODE
slot.
The sigil used for a typeglob is *
.
In our solution, we first create a reference to the symbol table:
my $symbol_table = do {no strict 'refs'; \%{$module . "::"}};
Then we iterate over the keys of the symbol table, and print out
each symbol for which there is a CODE
slot in the typeglob:
foreach my $symbol (keys %$symbol_table) {
say $symbol if *{$$symbol_table {$symbol}} {CODE};
}
Finally, we print BEGIN
, if there is an entry for BEGIN
in the
symbol table, but the glob does not have a CODE
slot. We need this
so we pass the given example:
say "BEGIN" if $$symbol_table {BEGIN} && !*{$$symbol_table {BEGIN}} {CODE};
Find the full program on GitHub.