use base and @ISA with package re-definition

Discussion in 'Perl Misc' started by bigmattstud@gmail.com, Mar 25, 2008.

  1. Guest

    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 ?
    , Mar 25, 2008
    #1
    1. Advertising

  2. Uri Guttman Guest

    >>>>> "b" == bigmattstud <> writes:

    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

    --
    Uri Guttman ------ -------- http://www.sysarch.com --
    ----- Perl Code Review , Architecture, Development, Training, Support ------
    --------- Free Perl Training --- http://perlhunter.com/college.html ---------
    --------- Gourmet Hot Cocoa Mix ---- http://bestfriendscocoa.com ---------
    Uri Guttman, Mar 25, 2008
    #2
    1. Advertising

  3. Guest

    On Mar 25, 2:57 pm, Uri Guttman <> wrote:
    > >>>>> "b" == bigmattstud <> writes:

    >
    > 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.
    , Mar 25, 2008
    #3
  4. Peter Scott Guest

    On Mon, 24 Mar 2008 18:34:07 -0700, bigmattstud wrote:
    > 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.

    --
    Peter Scott
    http://www.perlmedic.com/
    http://www.perldebugged.com/
    Peter Scott, Mar 25, 2008
    #4
  5. Guest

    On Mar 25, 11:09 pm, Peter Scott <> wrote:

    > 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
    , Mar 25, 2008
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. C Gillespie

    Using isA and getA in a python way

    C Gillespie, Sep 20, 2004, in forum: Python
    Replies:
    4
    Views:
    501
    Jeremy Bowers
    Sep 20, 2004
  2. ThatsIT.net.au

    RSS, XPathDocument and ISA proxy server

    ThatsIT.net.au, Mar 24, 2007, in forum: ASP .Net
    Replies:
    0
    Views:
    311
    ThatsIT.net.au
    Mar 24, 2007
  3. Egghead

    ISA and Web Services

    Egghead, Apr 17, 2007, in forum: ASP .Net Web Services
    Replies:
    0
    Views:
    100
    Egghead
    Apr 17, 2007
  4. Andrew Jocelyn
    Replies:
    0
    Views:
    668
    Andrew Jocelyn
    Nov 29, 2008
  5. Shahriar
    Replies:
    3
    Views:
    183
Loading...

Share This Page