Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

"use"ing from dynamic namespace

by dsheroh (Monsignor)
on Dec 01, 2008 at 16:32 UTC ( [id://727140]=perlquestion: print w/replies, xml ) Need Help??

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

I've been looking over my old code lately and finding a lot of projects where one has Foo::Module and another has an extremely similar Bar::Module, so I'm thinking the thing to do is factor out that commonality into Base::Module and derive both of the others from it.

Simple enough.

However, Foo::Module references Foo::Config, Bar::Module references Bar::Config, and each needs to talk to different databases, if nothing else, so the referenced modules need to remain distinct for each app's namespace, but I've had no luck in trying to track down a conventional way of accomplishing this.

What I've come up with so far is (in Base::Module):

BEGIN { my $ns = __PACKAGE__; $ns =~ s/::[^:]*$//; eval "require $ns\::Config"; "$ns\::Config"->import(qw(x y z)); }
as a replacement for use xxx::Config qw(x y z) which should pull in Base::Config from Base::Module, Foo::Config from Foo::Module, and Bar::Config from Bar::Module, but it really feels horribly ugly and clumsy to me.

Is this the standard way of accomplishing what I'm trying to do or is there a better solution out there?

Replies are listed 'Best First'.
Re: "use"ing from dynamic namespace
by perrin (Chancellor) on Dec 01, 2008 at 17:39 UTC
    I'd suggest using inheritance more. Either just define your load_config method in each one to do the right thing, or break it down even more with simple constant-like methods in each.

    package Base::Module; sub load_config { my $class = shift; my $module = $class->config_module; require $module; } package Foo::Module; use base qw(Base::Module); sub config_module { return 'Foo/Config.pm'; }
      Oddly enough, even though I was thinking of this in terms of "inheritance" and "polymorphism", it never occurred to me to take the extra step of actually turning the modules into classes. Thanks for pointing that out.

      This feels weirdly like dancing around strict refs. I know it's not.

      Would there be anything wrong with doing config_module in the base class as:

      sub config_module { return __PACKAGE__ . '::Config'; }

      Or such as that, anyway?

      for(split(" ","tsuJ rehtonA lreP rekcaH")){print reverse . " "}print "\b.\n";
        Would there be anything wrong with doing config_module in the base class as: ...

        ...the only problem is it wouldn't work ;) — at least not with perrin's code snippet.  One thing is that if you use require with a string (as opposed to a bareword), it must be a valid path fragment (i.e. / in place of ::), inlcuding the trailing .pm. The other thing is that when you declare config_module() in the base class, __PACKAGE__ would always be Base::Module, but the point of the exercise was to load different config variants (associated with Foo, Bar, etc.).

Re: "use"ing from dynamic namespace
by samtregar (Abbot) on Dec 01, 2008 at 17:43 UTC
    Maybe not important to you, but that won't work if your client code needs both Foo::Module and Bar::Module. The BEGIN block will only be run once. (And the code you posted doesn't work period since __PACKAGE__ is "Base::Module" in this case, not Foo::Module or Bar::Module!)

    Instead I think I'd have the base class define an init() method which the sub-classes call in a BEGIN:

       BEGIN { __PACKAGE__->init() }

    The code in init() could do roughly what you have in your BEGIN block, but I might make it a bit more configurable by using a method to get the config class name and defining that in each sub-class:

       sub config_class { "Foo::Config" }

    You could have a default implementation of that method in your base class that does the namespace manipulation.

    Does that make sense?

    -sam

      (And the code you posted doesn't work period since __PACKAGE__ is "Base::Module" in this case, not Foo::Module or Bar::Module!)

      Oops... Guess that makes it obvious that I hadn't actually tested this yet other than with the Base version...

      Does that make sense?

      Yep!

Re: "use"ing from dynamic namespace
by almut (Canon) on Dec 01, 2008 at 19:23 UTC

    Another common pattern would be to use explicit delegation (as opposed to inheritance, whose method dispatch can be considered a form of implicit delegation). I.e., as usual, put the stuff that remains after factoring out the commonalitities into separate modules that implement the same interface. Then have the base module delegate the respective functionality via method calls on whichever config/DB instance had been plugged in.

    It somewhat depends on the context whether it's more appropriate to instantiate those delegates from within the base module, or from the main program, passing them to the constructor. For example, when using the latter approach, it would roughly look like this:

    ... my $cfg; if (whatever) { $cfg = Foo::Config->new(...); } else { $cfg = Bar::Config->new(...); } my $obj = Base::Module->new( config => $cfg, ...); ...

    Generally, when things get complex, systems based on this simple form of delegation tend to remain more loosely coupled, with less interdependence among modules. In other words, they're often easier to modify in case the requirements should change.  At least that's my personal experience... but I'm pretty sure not everyone would agree :) — YMMV.

Re: "use"ing from dynamic namespace
by ikegami (Patriarch) on Dec 01, 2008 at 18:49 UTC
    BEGIN { my $ns = __PACKAGE__; $ns =~ s/::[^:]*$//; eval "require $ns\::Config"; "$ns\::Config"->import(qw(x y z)); }

    is the same as

    BEGIN { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; my @syms = qw(x y z); eval "use $ns\::Config \@syms"; }

    Then add error checking

    BEGIN { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; my @syms = qw(x y z); eval "use $ns\::Config \@syms; 1" or die $@; }

    Since you import, if can also do the job:

    sub qualify_ns { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; return "$ns\::$_[0]"; } use if 1, qualify_ns('Config') => qw(x y z);
Re: "use"ing from dynamic namespace
by ikegami (Patriarch) on Dec 01, 2008 at 18:52 UTC
    BEGIN { my $ns = __PACKAGE__; $ns =~ s/::[^:]*$//; eval "require $ns\::Config"; "$ns\::Config"->import(qw(x y z)); }

    is the same as

    BEGIN { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; my @syms = qw(x y z); eval "use $ns\::Config \@syms"; }

    Then add error checking

    BEGIN { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; my @syms = qw(x y z); eval "use $ns\::Config \@syms; 1" or die $@; }

    Since you import, if can also do the job:

    sub qualify_ns { ( my $ns = __PACKAGE__ ) =~ s/::[^:]*$//; return "$ns\::$_[0]"; } use if 1, qualify_ns('Config') => qw(x y z);

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://727140]
Approved by moritz
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (6)
As of 2024-04-24 09:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found