Optional Subclass Loading Problem

Peter Edwards peter at dragonstaff.com
Tue Jul 4 04:23:39 PDT 2006


The perl -c and eval or die approaches work quite well for me in
development, although if you don't set up the subclass in a BEGIN block you
won't know until you try and run the program whether the subclass fails.

I have a paged screen list base class that does similar stuff, using a
definition file to specify plugin sub-classes, that add extended methods
(within the same namespace) loaded on demand, or to specify named coderefs
to simple functions.

In my init method I do:

	# load any helper base classes
	if ( my $hc = $this->{listdef}->{'helperclasses'} )
	{
		for my $module (@$hc)
		{
			# check if loaded first?
			eval "require $module"
				or sm_Confess "list $this->{listdef_name}:
error loading helper class $module: $@";
			unshift @ISA, $module;
		}
	}

Then in call time code:

	# calculated search using helper functions, may add joins and where
clauses
	if ( my $fn = $listdef->{searchcalc}->{$field} )
	{
		my @arg = ( iv => $this->{iv}, field => $field, value =>
$val, wherestrptr => \$where, whereptr => \@where, dbptr => $db, dbdef =>
$dbdef, fromptr => \$from );
		if ( $this->can($fn) ) # method name
		{
			$this->$fn(@arg);
		}
		elsif ( defined &{$fn} ) # function pointer
		{
			no strict 'refs';
			&{$fn}($this, @arg);
			use strict 'refs';
		}
		else
		{
			sm_Confess "list $this->{listdef_name}: unknown
searchcalc function $fn for field $field";
		}
		next;
	}

Thinking about it, I suppose you could write a verify routine that "knows"
how to do all the requiring and then inspect @ISA and the respective package
hashes to see where method functions are defined and which ones will be
called. You could inspect a package for functions with code like this:

my $hp = inspect_package("main");
do something with $hp...

sub inspect_package
{
   my $package = shift || sm_Confess;
   my %h = ( functions => [], hashes => [], arrays => [], scalars => [],
filehandles => [],
      packages => [], systemscalars => [], packagescalars => [],
objectscalars => [] );
   no strict 'refs';
   my $tab = \%{$package.'::'};
   for (keys %$tab)
   {
      my $fullname = join '::', $package, $_;
      push (@{$h{hashes}}, "\%$fullname") if %$fullname && !($fullname =~
m/::$/);
      push (@{$h{packages}}, "\%$fullname") if %$fullname && ($fullname =~
m/::$/);
      push (@{$h{arrays}}, "\@$fullname") if @$fullname;
      if ( $$fullname ) # scalar pointer
      {
         if ( $_ =~ m/^\_\</ )
         {
            push @{$h{packagescalars}}, "\$$fullname";
         }
         elsif ( ($_ =~ m/[\000-\037]/) || (length($_) == 1) )
         {
            push @{$h{systemscalars}}, "\$$fullname = $$fullname";
         }
         elsif ( ref $$fullname )
         {
            push @{$h{objectscalars}}, "\$$fullname" . " (" .
ref($$fullname) . ")";
         }
         else
         {
            push @{$h{scalars}}, "\$$fullname = $$fullname";
         }
      }
      push (@{$h{functions}}, "\&$fullname") if defined &$fullname;
      push (@{$h{filehandles}}, "\*$fullname") if *{$fullname}{IO};
   }
   for ( keys %h )
   {
      @{$h{$_}} = sort @{$h{$_}};
   }
   return \%h;
}

(swiped out of a mod_perl perl status tool I wrote to be able to delve in
and out of packages and globals to see what persistent data is lurking in a
running application)

http://cpan.uwinnipeg.ca/htdocs/perl/NEXT.pm.html
http://cpan.uwinnipeg.ca/htdocs/perl/ISA.pm.html
http://cpan.uwinnipeg.ca/htdocs/Class-C3/Class/C3.pm.html
might be useful for more ISA/package inspection examples.

You'll still be screwed if you do bad things in AUTOLOAD functions :-)

Regards, Peter

-----Original Message-----
From: miltonkeynes-pm-bounces+peter=dragonstaff.com at pm.org
[mailto:miltonkeynes-pm-bounces+peter=dragonstaff.com at pm.org] On Behalf Of
Nik Clayton
Sent: 04 July 2006 10:54
To: miltonkeynes-pm at pm.org
Subject: Re: Optional Subclass Loading Problem

Tom Hukins wrote:
[...]
> This works fine until a subclass fails to compile due to the
> programmer (me) making a mistake while adding new code.  The "eval use
> ..." fails so it seems like methods have suddenly vanished.  If the
> method exists in the Thing superclass, the methods get called with
> incomplete functionality which confuses the poor programmer.
> 
> So, can anyone think of a good way I could distinguish between
> different types of failure I might encounter and an elegant way of
> dealing with them?  I want to ignore "subclass not found errors", as
> not all types of thing currently (or will ever) have their own
> subclass.  So far, the only other type of error I have encountered is
> a compilation error, but might I encounter others (eg. file unreadable
> due to permissions)?

It doesn't solve the problem you pose, but could you work around it by
having a test that does the equivalent of

     find . -name \*.pm | xargs perl -c

to make sure that all the .pm files compile correctly?  That way you
could be reasonably certain that if the eval fails it's because there
was a problem with the load.

Alternatively, create subclasses for everything, even if some of them
are no more than:

    package Thing::foo;

    use base qw(Thing);

    1;

Taking a look at "perldoc require" it's got Perl code that claims to be
equivalent to require in it, complete with calls to die().  It may be the
case that that code also shows all the ways in which require() might
fail, in which case you could use that.  That's still quite fragile though.

Ah, here's a thought.  You can check %INC after the use.  I've just done
a quick test.

=== t.pl ===
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

use lib '.';

eval "use Works;";
eval "use Syntax;";
eval "use doesNotExist;";

print Dumper \%INC;
=== end ===

=== Works.pm ===
package Works;
1;
=== end ===

=== Syntax.pm ===
package Syntax;
sob { print "boo hoo\n"; }  # Syntax error on this line
1;
=== end ===

If you run t.pl you'll see that there are entries for Works.pm and
Syntax.pm, indicating that both of those were found (even though
Syntax.pm has a syntax error).

There's no entry for "doesNotExist.pm", indicating that the file
couldn't be found.

So workable logic is probably:

    eval "use $subclass";
    if($@) {
        # Rethrow the error if the module was found but couldn't
        # be compiled
        die $@ if exists $INC{$self->name() . 'pm'};
    }

N

_______________________________________________
MiltonKeynes-pm mailing list
MiltonKeynes-pm at pm.org
http://mail.pm.org/mailman/listinfo/miltonkeynes-pm




More information about the MiltonKeynes-pm mailing list