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

Dear Monks,

i have implemented the composite pattern of the gang of four fame with perl.

Basically i have a perl module, which loads and instanciates perl objects and puts them into a hash.

And i have a function, which iterates through the hash and calls the same function of the perl objects in the hash, something like this:

sub log { my $self = shift; my $sender = shift; my $message = shift; my $logger = undef; foreach $logger ($self->loggers()) { $logger->log($sender, $message); } }

Everthing fine so fare, except a little disturbing thing, that for each new modul i want to use through the composite interface i include a new use statement.

Surely not the most elegant way to add new functionality.

So humbly i ask the Perl Monks if there isn't a more wiser way to do this, to avoid this clumsy addition of a "use" statement for each new Module.

Yours, a humble seeker of Perl wisdom,

Chris

Replies are listed 'Best First'.
(jeffa) Re: Generic Composite Interface
by jeffa (Bishop) on Mar 23, 2002 at 16:45 UTC
    merlyn is right. Personally, while maybe not the most elegant way to do so, having to include a new use statement for each new module has a self-documenting quality to it. But it is indeed error prone.

    What you could do is scan a particular directory for .pm files in a BEGIN block like so:

    use strict; use Data::Dumper; BEGIN { # scan the current directory eval{require $_} for <*.pm>; }; my @class = ( Foo->new, Bar->new, Baz->new, ); print Dumper $_ for @class;
    This works (assuming that the corresponding modules exist and are free of compilation errors) , but without precautions it will really be more error prone than just using the modules explicitly. Neat stuff though .... Perl rules! :)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    

      I think that's what i am looking for. So the:

      BEGIN { # scan the current directory eval{require $_} for <*.pm>; };

      substitutes the "use" declarations?

      I absolutely agree, this should'nt the recommended or normal style to add new functionality, but i context of my application it certainly helps.

      Chris

        jeffa

        From 'perldoc -f use':
        Imports some semantics into the current package
        from the named module, generally by aliasing
        certain subroutine or variable names into your
        package.  It is exactly equivalent to
        
           BEGIN { require Module; import Module LIST; }
        
        except that Module must be a bareword.
        
        Turns out that evaling the way i did is not very useful, better to stick with the traditional for loop syntax like so to take advantage of 'exceptions':
        BEGIN { for (<*.pm>) { eval {require $_}; warn $@ if $@; # but it's prolly going to die anyway! } };
        So you can more easily find which module(s) and what line(s) caused the error(s). Also, defining a path to the directory that contains the modules would be a good thing to do. Good luck! :)
        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
•Re: Generic Composite Interface
by merlyn (Sage) on Mar 23, 2002 at 16:09 UTC
    for each new modul i want to use through the composite interface i include a new use statement.
    I don't know why you find that surprising. A "use" is a declaration by you that this file needs more functionality. Certainly, you can hide some of that with autoloads and such, but why not be explicit?

    -- Randal L. Schwartz, Perl hacker

      Agreed, maybe i was'nt precise enough i my statement:

      I do not find the fact that i include a "use" declaration for new functionality suprising, this is i what i normally expect and want.

      Also i maybe did'nt provide enough information about the the context of the "application" i was writing: a "little" monitoring daemon. The function,which instanciates the objects, does this by reading from file and instanciates them by parsing the input from the file, something like the following:

      sub load { my ($self, $filename) = @_; open (LOGGERS, "< " . $filename); my ($line); while ($line = <LOGGERS>) { chomp $line; if (!$line) { last;} my @parms = split /:/, $line; my $loggertyp = shift @parms; my $logger = ${loggertyp}->new(); $logger->systemname($self->systemname); $logger->subject($self->subject); while (@parms) { my $parmtyp = shift @parms; my $parmvalue = shift @parms; ${logger}->${parmtyp}($parmvalue); } $self->loggers($logger); } close(LOGGERS); }

      It actually knows nothing about "functionality" it makes available.

      In this context it would be a great thing, if i just could write a new perl module, change the file and the tell the daemon through some signal, to newly load the file and the "daemon" executes new functionality,granteed the whole thing is well tested etc. With the "use" declarations, i additionally have to stop the "daemon" and change the source code of this module. So that's the reason, that in this context, i would prefer not be be explicit.

      Chris
        This is your problem here:
        my $logger = ${loggertyp}->new();
        If you're gonna call new, you're gonna have to ensure that the package is loaded. See how the LWP moudules do it for the various protocol handlers. It's a mere matter of coding up a proper require dynamically. No big deal, but you have to do it, true.

        Or, you can add something like the following subroutine (untested):

        sub UNIVERSAL::AUTOLOAD { my ($pack, $meth) = $AUTOLOAD =~ /(.*)::(.*)/; die "do you really want me to pull in $pack for $meth?\n" unless $me +th eq "new"; eval "require $pack"; die $@ if $@; die "$pack didn't define $meth" unless defined &$AUTOLOAD; goto &$AUTOLOAD; }

        -- Randal L. Schwartz, Perl hacker