pdt1630 has asked for the wisdom of the Perl Monks concerning the following question:

I am in the middle of rewriting a p.o.s. tool we have here at work... I want to rewrite it in a manner that the rest of the villiage idiots that work here can add functionality to the tool after I leave, without destroying the original intent of the code.

The question I have is what is the best way to dynamically use modules and then call those modules from one perl script. I want to design a controller module that reads in a list of modules it needs to control and interacts with those modules appropriatly, based on the specified interaction method (all defined in a config file).

The intent would be then that someone would be able to come in, read the "design template" to which new functionallity had to meet, add a line to the config file and not have to open my code up at all. Otherwise, I'll leave for vacation and someone will bastardize my codeline and I'll spend a week trying to figure out what they screwed up...

I am rather new to perl, so I'm looking for some advice before I dive headlong into this project which may or maynot be physically possible.

Cheers

  • Comment on Best/possible ways to dynamically use modules

Replies are listed 'Best First'.
Re: Best/possible ways to dynamically use modules
by chipmunk (Parson) on Dec 18, 2001 at 09:21 UTC
    Conditionally loading a module is very easy. The only thing to avoid is a bare use statement, because use is executed at compile time. For example:
    if ($use_socket) { use Socket; # oops! Socket will get loaded at compile-time # before $use_socket is even evaluated }
    So, you can either eval a use statement, or require the module and then call import. (I prefer the second approach.)
    if ($use_socket) { eval "use Socket"; # ok: Socket gets loaded at run-time # if $use_socket is true }
    if ($use_socket) { require Socket; # ok: Socket gets loaded at run-time Socket->import(); # if $use_socket is true }

     

    However, you may need more than that, because you're planning to load the modules from a controller module. If the modules you're loading export anything, you probably want to export them into the package that called the controller module. That is, maybe you'll have the controller module load Socket, but then you want to call the Socket functions from the main script.

    The Exporter module, which provides a basic import() method, also provides export_to_level(). export_to_level() does an import to a package farther up in the call tree. For modules which inherit from Exporter, such as the Socket module, you can call the export_to_level() method to get the behavior you need:

    if ($use_socket) { require Socket; Socket->export_to_level(1, @_); }
    Unfortunately, not every module inherits from Exporter or defines its own export_to_level() method. CGI, for example, doesn't have an export_to_level() method. I'm afraid I don't know a good approach for a situation like this. But I hope this was enough to get to you started!
      You can always fool around with a goto:
      { my %is_imported; sub lazy_use { my ($class, @args) = @_; unless ($is_imported{"$class @args"}++) { my $file = $class; $file =~ s-::-/-g; $file .= ".pm"; require $file; my $importer = UNIVERSAL::can($class, "import"); goto &$importer if defined $importer; } } }
      and now you can issue many calls to the lazy_use routine and it will parse once and import once. In fact this routine could be exported to other namespaces and should work correctly.
        Is there any reason to use that goto instead of simply calling the sub, or are you just being perverse?
Re: Best/possible ways to dynamically use modules
by mstone (Deacon) on Dec 18, 2001 at 06:33 UTC

    Start by defining the protocol that your controller will use to control the modules.

    You'll also need to decide what kind of architecture you want. Are you building a framework, where you plug data-source modules into one side and data-display modules into the other? Are you building a layered system, where you can pull a single item from each pool of feet, legs, torsos, and heads? Are you building a background server, where the module builds a query, then passes it to the back-end which does the actual crunching?

    Figure out what the pieces are, then decide what they need to say to each other. Once you have that, you can weigh the alternatives of various implementations.. objects and methods, data structures and global functions, etc. Among the decisions you make will be the protocol for letting the core know what modules are available, and what services they require.

    mike
    .

      Mike

      I think what you are suggesting is using a communications protocol of some sort, which was a little more than I was thinking, but a very good idea none the less... (gotta remember to think outside the box)

      As far as the application architecture, I was looking to create a hub and spoke effect... contoller module plus all the functional modules working independently of each other... the technical architecture would be layered and statically available for all. The architecture problem I inedequately described was the simple fact that I can't keep good help arround. I must build something that is flexible enough to allow anyone with some perl skills to plug in a new functional module without much experience or knowledge of the whole system.

      I guess the quick answer (and hopefuly simplest)I was looking for was what are my options if I want to be able dynamically load modules into a perl program and then subsequently call them through out the program. From the research I've seen it might be possible to open the config file, parse through and loop through with an "eval" statement at every module...

      Functional interaction between modules would again be determined by config file at load time.

      This might a lot of work for little reward, but damn... the concept of it sounded pretty cool at work...

      Cheers


        > I think what you are suggesting is using a communications protocol of
        > some sort, which was a little more than I was thinking, but a very
        > good idea none the less... (gotta remember to think outside the box)

        Whoops.. accidentally fell into personal shorthand when talking to someone outside my own head. I didn't mean 'protocol' in the sense of adhering to TCP/IP, COM, or some other established standard. What I meant was something along the lines of 'implementation independent interface specification'.

        See, when I think 'interface spec', I think about function signatures:

        set_value (hash_key: string, key_value: string) : nil get_value (hash_key: string) : string
        and while that's a necessary step on the road to shippable code, it's a truly rotten place to start. There's too much detail, and it's far too easy to get so bogged down in the minutae of 'how it works' that you lose sight of the big question: 'what does it do?'

        So.. I was really just suggesting you decide how to partition the system, then figure out what the pieces need to say to each other.


        > I guess the quick answer (and hopefuly simplest)I was looking for was
        > what are my options if I want to be able dynamically load modules
        > into a perl program and then subsequently call them through out the
        > program. From the research I've seen it might be possible to open the
        > config file, parse through and loop through with an "eval" statement
        > at every module...

        I think I see where you're going. Yes, you can eval() code into a program:

        $code = <<DONE; sub func { print "hello, world.\n"; } DONE eval $code; &func;

        and you can get that code from an external file:

        $file = '/some/file/path'; open (CODE, $file) or die qq(can't read "$file": $!); $tmp = $/ ; undef $/; $code = <CODE>; $/ = $tmp; close CODE; eval $code; ## and then run some function defined in that code.

        mike
        .

Re: Best/possible ways to dynamically use modules
by dragonchild (Archbishop) on Dec 18, 2001 at 16:25 UTC
    Just a thought:

    Use RCS (or some other source control system) to protect yourself. If you don't want just anyone just walking in, commenting use stricts; and adding three globals, then control your source. You also get a log of who edited which source file when (and, if they're polite, what they did).

    Another thought would be to go about getting all those "village idiots" to learn "The Right Way"(tm) to do things so as to not break your "Perfect Algorithms"(tm). Maybe they have a few ideas you didn't think of. Maybe one's a genius in disguise. Maybe not, but you'd never know it to look at them. :-)

    More importantly, though, you start to get past the "Us vs Them" mentality that so many programmers have towards their users/colleagues/coworkers/village idiots. Maybe, just maybe, you can get one of them to evolve into a higher lifeform. Maybe a VB programmer. *grins*

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.