use base and @ISA with package re-definition

B

bigmattstud

I'm trying to load multiple classes with the same name from different
directories in order to implement a pluggable deployment system. I
have managed to get the classes to be redefined by playing with the
global symbol table, and it seems to be behaving the way I want except
for the definition of @ISA. Here's some sample code:

Here's the driving script:

# Create a class and instantiate it from the first directory
print "Constructing MyMod from D:/Data/Perl/Module1\n" ;
push @INC,'D:/Data/Perl/Module1' ;
require MyMod ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;
print "\n" ;

# Clean out the previous definition of MyMod by deleting the entries
from the INC hash and the global symbol
# table
delete $INC{"MyMod.pm"} ;
delete $main::{'MyMod::'} ;
pop @INC ;

# Create a class and instantiate it from the second directory
print "Constructing MyMod from D:/Data/Perl/Module2\n" ;
push @INC,"D:/Data/Perl/Module2" ;
require MyMod ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;

Here's the definition of MyMod from the D:/Data/Perl/Module1 directory

package MyMod;
use strict;
use base qw(BaseBuild1) ;

sub new {
my $class = shift ;
my $self = $class->SUPER::new();
return $self;
}

1;

and the base class for this is:

package BaseBuild1;
use strict;

sub new {
print "Inside BaseBuild1 constructor\n" ;
my $class = shift;
my $self = bless {}, $class;
return $self;
}

1;

Here's the definition of MyMod from the D:/Data/Perl/Module2 directory

package MyMod;
use strict;
use base qw(BaseBuild2) ;

sub new {
my $class = shift ;
my $self = $class->SUPER::new();
return $self;
}

1;

and here's the base class

package BaseBuild2;
use strict;

sub new {
print "Inside BaseBuild2 constructor\n" ;
my $class = shift;
my $self = bless {}, $class;
return $self;
}

1;


This is the output:

Constructing MyMod from D:/Data/Perl/Module1
MyMod is a BaseBuild1
Inside BaseBuild1 constructor

Constructing MyMod from D:/Data/Perl/Module2
MyMod is a BaseBuild1
Inside BaseBuild2 constructor

So the behaviour seems correct (in that it's doing what I want), but
Perl seems to be stubbornly holding onto the definition of MyMod as a
BaseBuild1 (even though the call to SUPER::new goes to the correct
constructor). So my questions are:

* Does this matter at all ?
* Is there any way to fix it ?
* Is there a better way to do what I want to do ?
 
U

Uri Guttman

b> I'm trying to load multiple classes with the same name from different
b> directories in order to implement a pluggable deployment system. I
b> have managed to get the classes to be redefined by playing with the
b> global symbol table, and it seems to be behaving the way I want except
b> for the definition of @ISA. Here's some sample code:

you seem to be going about this in a very bizarre and tricky way. it is
much easier to dynamically load modules with different names (maybe
under the same higher level namespace) and use OO polymorphism to call
into them as needed. no muss no fuss!

b> Here's the driving script:

b> # Create a class and instantiate it from the first directory
b> print "Constructing MyMod from D:/Data/Perl/Module1\n" ;
b> push @INC,'D:/Data/Perl/Module1' ;
b> require MyMod ;
b> print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
b> $build = MyMod->new() ;
b> print "\n" ;

what decides which of these modules (or more than one?) to load?

b> # Clean out the previous definition of MyMod by deleting the entries
b> from the INC hash and the global symbol
b> # table
b> delete $INC{"MyMod.pm"} ;
b> delete $main::{'MyMod::'} ;
b> pop @INC ;

ewww.

b> So the behaviour seems correct (in that it's doing what I want), but
b> Perl seems to be stubbornly holding onto the definition of MyMod as a
b> BaseBuild1 (even though the call to SUPER::new goes to the correct
b> constructor). So my questions are:

b> * Does this matter at all ?
b> * Is there any way to fix it ?
b> * Is there a better way to do what I want to do ?

yes. drop all your conceptions about how to do this (call delete on this
idea in your brain! :).

it is MUCH easier than you realize to do it with method calls. this is a
bit of pseudo code but it should be very easy to make it real.

in Foo/Module1.pm:

package Foo::Module1 ;

use base Foo ;

sub new {

#construct away
}

in Foo/Module2.pm:

package Foo::Module2 ;

use base Foo ;

sub new {

#construct away
}

now you have two sibling modules ready to be loaded. you just decide to
load one (or both) and instantiate them via their different names:

my $variation = 'Module1' ; # this can be set from anywhere
my $class = "Foo::$variation" ;

# this is a rare use of string eval that is very useful

eval "require $class" or die "can't load $class $@" ;

my $foo_type = $class->new( @args ) ;

# and now we want an object of the other variation. just do the same
# code and set $variation to Module2. or make that code into a sub and
# you can construct variant objects on the fly:

sub make_foo_obj {

my( $variantion, @args ) = @_ ;

my $class = "Foo::$variation" ;

# this is a rare use of string eval that is very useful

eval "require $class" or die "can't load $class $@" ;

return $class->new( @args ) ;
}

see, simple and effective. this works because perl does late binding and
can use a dynamically made class name to both load modules and to call
their constructors. this is the best and easiest way to support variant
modules. your way will lead to insanity (or has led you there already! :)

uri
 
B

bigmattstud

you seem to be going about this in a very bizarre and tricky way. it is
much easier to dynamically load modules with different names (maybe
under the same higher level namespace) and use OO polymorphism to call
into them as needed. no muss no fuss!
Yes, I'd agree with that. I probably need to include some background,
this is actually a system for handling deployment of code from a
source control system. All code needs to be extracted from source
control (based on tags) and copied to some target directory, but some
file types need to have some sort of 'execution ready' behaviour
performed on them after extraction, eg Java programs need to be
compiled and SQL files need to be executed in a database. Our current
system does this using Ant, by placing a build.xml file in the target
directory. The build.xml file serves two purposes: (a) its presence
indicates that some further action is required and (b) the contents
specify what action(s) to perform. If a build.xml is found in the
target directory Ant gets invoked and the required actions are
performed.

I started to get sick of the limitations of Ant, and since the wrapper
script which was handling this deploy was in Perl I decided to try and
implement a similar concept. Each target directory would contain a
very small Perl class which could extend various base classes to
implement specific deployment behaviours. Since there are many of
these directories, and the classes are not required after that
directory has been processed, I thought it was easier to keep the same
name rather than trying to manage the namespace clashes within the
modules.
what decides which of these modules (or more than one?) to load?
As above, the wrapper script moves through the target deploy
directories and loads the module if it finds one.
b> # Clean out the previous definition of MyMod by deleting the entries
b> from the INC hash and the global symbol
b> # table
b> delete $INC{"MyMod.pm"} ;
b> delete $main::{'MyMod::'} ;
b> pop @INC ;

ewww.
Yes, I thought the same, that's why I asked the question. It did seem
to work though.
 
P

Peter Scott

I'm trying to load multiple classes with the same name from different
directories in order to implement a pluggable deployment system. I
have managed to get the classes to be redefined by playing with the
global symbol table, and it seems to be behaving the way I want except
for the definition of @ISA. Here's some sample code:

Here's the driving script:

# Create a class and instantiate it from the first directory
print "Constructing MyMod from D:/Data/Perl/Module1\n" ;
push @INC,'D:/Data/Perl/Module1' ;
require MyMod ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;
print "\n" ;

# Clean out the previous definition of MyMod by deleting the entries
from the INC hash and the global symbol
# table
delete $INC{"MyMod.pm"} ;
delete $main::{'MyMod::'} ;
pop @INC ;

# Create a class and instantiate it from the second directory
print "Constructing MyMod from D:/Data/Perl/Module2\n" ;
push @INC,"D:/Data/Perl/Module2" ;
require MyMod ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;
[snip]

This is the output:

Constructing MyMod from D:/Data/Perl/Module1
MyMod is a BaseBuild1
Inside BaseBuild1 constructor

Constructing MyMod from D:/Data/Perl/Module2
MyMod is a BaseBuild1
Inside BaseBuild2 constructor

Your problem stems from the line

delete $main::{'MyMod::'} ;

which falls into the "don't do that" category. There have been attempts
in the past to patch perl to issue a warning or error on such statements.
Even now it is fraught with danger:

$ perl -e 'delete $main::{"foo::"}; push @foo::ISA, "bar"'
Segmentation fault

Oops. If you want further proof that this is too dangerous to use, run
your program under the debugger and right before the second statement

print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;

examine the value of @MyMod::ISA. Scary.

Uri's answer is the best help. If you wanted to persist in the direction
you were going, instead of deleting the whole symbol table, just empty
@MyMod::ISA and turn off subroutine redeclaration warnings.
 
B

bigmattstud

Uri's answer is the best help. If you wanted to persist in the direction
you were going, instead of deleting the whole symbol table, just empty
@MyMod::ISA and turn off subroutine redeclaration warnings.

I'm not sure that Uri's answer is going to help in my case because of
difficulties in ensuring that there will never be namespace conflicts,
but I did try your suggestion and it seems to work better. I also
cleaned it up to remove a bit more of the other magic being done.

# Create a class and instantiate it from the first directory
print "Constructing MyMod from D:/Data/Perl/Module1\n" ;
require 'D:/Data/Perl/Module1/MyMod.pm' ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;
print "\n" ;

# Clean out the previous definition of MyMod
undef @MyMod::ISA ;

# Create a class and instantiate it from the second directory
print "Constructing MyMod from D:/Data/Perl/Module2\n" ;
require 'D:/Data/Perl/Module2/MyMod.pm' ;
print "MyMod is a ",join(',',@MyMod::ISA),"\n" ;
$build = MyMod->new() ;

This is the output:

Constructing MyMod from D:/Data/Perl/Module1
MyMod is a BaseBuild1
Inside BaseBuild1 constructor

Constructing MyMod from D:/Data/Perl/Module2
MyMod is a BaseBuild2
Inside BaseBuild2 constructor

Thanks for your help
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,738
Latest member
JinaMacvit

Latest Threads

Top