tags:

views:

316

answers:

5

I'd like to create several modules that will be used in nearly all scripts and modules in my project. These could be used in each of my scripts like so:

#!/usr/bin/perl

use Foo::Bar;
use Foo::Baz;
use Foo::Qux;
use Foo::Quux;

# Potentially many more.

Is it possible to move all these use statements to a new module Foo::Corge and then only have to use Foo::Corge in each of my scripts and modules?

+6  A: 

Something like this should work:

http://mail.pm.org/pipermail/chicago-talk/2008-March/004829.html

Basically, create your package with lots of modules:

package Lots::Of::Modules;
use strict; # strictly optional, really

# These are the modules we want everywhere we say "use Lots::Of::Modules".
# Any exports are re-imported to the module that says "use Lots::Of::Modules"
use Carp qw/confess cluck/;
use Path::Class qw/file dir/;
...

sub import {
    my $caller = caller;
    my $class  = shift;

    no strict;
    *{ $caller. '::'. $_ } = \*{ $class. '::'. $_ }
       for grep { !/(?:BEGIN|import)/ } keys %{ $class. '::' };
}

Then use Lots::Of::Modules elsewhere;

use Lots::Of::Modules;
confess 'OH NOES';
jrockway
This will fail to export functions containing the word "BEGIN" or "import" (e.g. a function named "do_important_work()") -- change the regex to /\A(?:BEGIN|import)\z/ to fix this. It also needs a true expression (e.g. "1;") as the last statement.
j_random_hacker
More subtly, it tramples on $f, @f, %f etc. when exporting a function f(). I'm not sure how this could be fixed.
j_random_hacker
By adding {CODE} after the second glob.
jrockway
@jrockway: That's assuming the package wanted to export a function f(). What if they wanted to export a scalar $f or an array @f or a hash %f instead? There is no way to tell.
j_random_hacker
+3  A: 

Yes.

In Foo/Corge.pm

use Foo::Bar;
use Foo::Baz;
use Foo::Qux;
use Foo::Quux;

1;   # Be successful

All that is left is to get the directory containing sub-directory Foo added to your library path (@INC). Alternatively, create Foo.pm and have it use the other modules. They would be in a Foo sub -directory beside Foo.pm.

If you think about it, all the complex Perl modules that use other modules do this all the time. They are not necessarily in the same top-level package (Foo in this example), but they are used just as necessarily.

While you could use Carp, and Path::Class and confess, and so on (as jrockway suggests), that seems like overkill from where I'm sitting.

Jonathan Leffler
My example assumes you want the exported symbols from each module. Path::Class and Carp are examples of modules that export symbols. When you "use" Lots::Of::Modules, you get the exports of everything you used when defining Lots::Of::Modules.
jrockway
+7  A: 

Yes, it is possible, but no, you shouldn't do it.

I just spent two weeks to get rid of a module that did nothing but use other modules. I guess this module started out simple and innocent. But over the years it grew into a huge beast with lots and lots of use-statements, most of which weren't needed for any given run of our webapp. Finally, it took some 20 seconds just to 'use' that module. And it supported lazy copy-and-paste module creation.

So again: you may regret that step in a couple of months or years. And what do you get on the plus side? You saved typing a couple of lines in a couple of modules. Big deal.

innaM
If the modules combined together have nothing in common, I totally agree, but grouping together cohesive, related modules is actually a good thing to do, the same way it's good to group many cohesive lines of code into a function in one place and then call it many times.
j_random_hacker
I completely disagree. Sorry. What if all the modules did basically the same thing, with slight variations? Why use all of them all the time and not just the one you need right now? For grouping stuff that belongs together, we have package namespaces in Perl.
innaM
If all the modules did basically the same thing, sure, it makes sense to use just one rather than all of them. But if you're dealing with a set of complementary modules, why not group them? The only downside I can see is possible performance issues if you let groups get huge.
j_random_hacker
I disagree. Plenty of other languages do this -- see ASDF in CL. The only way to use a module is to use it everywhere.
jrockway
The only way to use a module is to use it everywhere? - I'm sorry, but I have NO idea what that means.
innaM
A: 

[EDIT: My earlier solution involving use Lots::Of::Modules; had a subtle bug -- see bottom. The fix makes things a bit uglier, but still workable.]

[EDIT #2: Added BEGIN { ... } around the code to ensure that any functions defined are available at compile time. Thanks to jrockway for pointing this out.]

The following code will do exactly what jrockway's code does, only simpler and clearer:

In Lots/Of/Modules.inc:

use Carp qw/confess cluck/;
use Path::Class qw/file dir/;

0;   # Flag an error if called with "use" or "require" instead of "do"

To import those 4 functions:

BEGIN { defined( do 'Lots/Of/Modules.inc' ) or die; }

Because we don't have a package Lots::Of::Modules; statement at the start of this file, the use statements will export into the caller's package.

We must use do instead of use or require, since the latter will only load the file once (causing failure if use Lots::Of::Modules; is called more than once, e.g. in separate modules used by the main program). The more primitive do does not throw an exception if it fails to find the file named by its argument in @INC, hence the need for checking the result with defined.

j_random_hacker
This doesn't work if you are relying on the functions to be available at compile-time, since do runs at runtime. The side-effect is that prototypes on the imported functions won't work.
jrockway
Good point jrockway. I've added a BEGIN block to correct this.
j_random_hacker
This is also flawed in that it relies on a hardcoded path rather than searching @INC as 'use' does. No big deal for local development, but have fun when you try to install it somewhere else...
Dave Sherohman
@Dave Sherohman: I agree that is a problem. The main problem is that "use" is *almost* exactly what we want -- if there was some way to force it to *always* include the file, this ugly workaround could be avoided. (Maybe "BEGIN { local %INC; use Lots::Of::Modules; }"?)
j_random_hacker
use = require + import. require runs once, import runs every time.
jrockway
A: 

Another option would be for Foo::Corge to just re-export any items of interest normally:

package Foo::Corge;

use base 'Exporter';
BEGIN {
  our @EXPORT_OK = qw( bar baz qux quux );

  use Foo::Bar qw( bar );
  use Foo::Baz qw( baz );
  use Foo::Qux qw( qux );
  use Foo::Quux qw( quux );
}
1;

(The 'use' statements can probably go outside the BEGIN, but that's where they were in the code I checked to verify that this worked the way I thought it did. That code actually evals the uses, so it has a reason for them to be inside BEGIN which likely doesn't apply in your case.)

Dave Sherohman