Long Import Lists (and available strategies for managing them)

Long Import Lists (and available strategies for managing them)

I ran across this the other day while writing some sample code for the next chapter of Testing Strategies for Modern Perl.

How can we use long lists of symbols from an imported package and still keep the code readable?

I usually prefer use statements of the form:

use My::Module qw(symbol1 symbol2 symbol3);

Except for specially understood modules, like Moose and Test::More, I don’t like to just import everything. Rather I like to explicitly call out only the specific symbols I need.

But what if you need to:

use My::Module qw(
    symbol1 symbol2 symbol3 symbol4 symbol5 etc and so many symbols
    that it takes up several lines all the time in every package
    that uses it
);

There are a few alternative approaches.

Just import everything

We could just fall back on importing everything.

So… Five years from now, I’m going to revisit that code, and I’ll see:

wiggle_foo_gadget(WIGGLE_WOBBLE);

And my first question is going to be, “Which of those 17 modules does that come from?!” And then I’ll need to grep through the entire codebase to find it.

That’s why I like to call out the specific symbols being imported, and that’s our standard practice here at The Perl Shop. If the symbol is explicitly imported, a simple search through the current module will find it, and it will be clear where the symbol came from.

Additionally, when we import everything, we have zero control over what symbols are imported. If a later version of a used module exports more symbols, then they’ll automatically get imported in our package, whether we want them there or not. This could result in name clashes and action-at-a-distance bugs down the line.

So alternative option #2…

Don’t import anything

That is, we can just use the full package name. So:

use My::Module ();

do_something_with($My::Module::scalar_var);
do_something_else_with(@My::Module::array_var)
My::Module::do_the_hustle();

This is generally my preferred alternative. Additionally, reading My::Module::do_the_hustle(), having the module cited adds context and can make the code easier to read.

But what if My::Module is actually more like My::External::Module::With::Several::Levels? Copying and pasting that monstrosity everywhere, that’s going to get pretty confusing pretty fast.

And in my sample code, this was indeed what I was facing. So not a viable alternative.

Use import tags

This is a well-established idiom, implemented directly by Exporter.

With a tag, the use line would look something like this:

use My::Really::Long::Module::Name qw(:some_symbols);

This is much more concise, and it gives us at least some level of control over what is being imported. But it still doesn’t make clear where the symbols came from.

This is the option I chose in my sample code. Mostly I did so for the reasons above. In production code, I would probably have opted for the very last of the options below (“Lexical aliasing”). But in this case, it was sample code for a testing book, and I really didn’t want to take a section of the book to explain that non-standard idiom, because it had nothing to do with testing.

Export a hash of symbols

This is very nonstandard, but one thought that occurs to me is that one could export read-only hashes of symbols.

So in My/Really/Long/Module/Name.pm:

package My::Really::Long::Module::Name;

use Readonly;

Readonly::Hash our %some_symbols => (
    symbol1 => \&symbol1,
    symbol2 => \&symbol2,
    symbol3 => \&symbol3,
    # ... and so forth
);

Then we can:

use My::Really::Long::Module::Name qw(%some_symbols);

$some_symbols{symbol1}();
$some_symbols{symbol2}();
$some_symbols{symbol3}();

That’s a little awkward, but it is succinct (at least in the using module) and does identify the symbol source.

But it doesn’t exactly work with variables. That is, as soon as you put a reference to anything in a read-only data structure, the anything itself becomes read-only.

There’s a better alternative…

Alias the used package

That is, use My::Really::Long::Module::FooBar and then refer to it as simply FooBar.

We can accomplish this with Package::Alias:

use Package::Alias FooBar => 'My::Really::Long::Module::FooBar';

FooBar::make_it_rain();
say $FooBar::is_raining;

The Package::Alias line above is actually syntactic sugar for:

BEGIN {
    use My::Really::Long::Module::FooBar;
    *{FooBar::} = \*{My::Really::Long::Module::FooBar::};
}

The disadvantage here is that the alias is global, not lexical. That is, if one module aliases FooBar, then another module can’t also alias FooBar (whether or not they’re aliasing to the same target). Package::Alias will warn if you attempt that and then ignore the second and subsequent attempts.

(Note also that Package::Alias will load the target package into the caller’s namespace only if it hasn’t been used previously. And it actually does use the default use, which imports all available symbols into the caller’s namespace—but only if no other package has formerly loaded it. For this reason, this functionality is only appropriate for packages that do not export symbols. Otherwise, you need to explicitly use My::Module () before using Package::Alias.)

Lexical aliasing

What I’d really like is to alias the target in lexical scope.

namespace::alias claimed to do that. It uses Perl internals to accomplish its black magic. Unfortunately, there are several critical documented issues, and it has not successfully built since Perl 5.18. It was last released in 2012. So not an option.

aliased does something similar. It creates a short-named subroutine that returns the long name of a target package. It also has magic to figure out what the subroutine should be named and other features. I haven’t tried the module, but it looks like quite an elegant design: simple and powerful. Unfortunately, it really only works for object-oriented code. So a tool for the toolbox, but not applicable to this blog post.

The best I was able to come up with was this:

use My::Really::Long::Module::FooBar ();

my $FooBar = \%{My::Really::Long::Module::FooBar::};

$FooBar->{do_something}();
$FooBar->{do_something_else}();
my $is_done = $FooBar->{is_it_done}();

$FooBar->{scalar_var}->$* = 'foo';

push $FooBar->{array_var}->@*, qw(bar baz qux quux);

$FooBar->{hash_var}{spam} = 'eggs';

This is slightly awkward, but it works. And it’s a nonstandard idiom, but it’s not so confusing that a competent Perl programmer can’t quickly figure out what’s going on. And maybe if we used it more, it would catch on.

This is an idiom I will keep in mind for future projects.


Update

On the blogs.perl.org thread for this post, Yang Bo and I had a conversation about ways to provide (or simulate) lexical aliasing of packages, and he wrote this bit of code:

package alias {
    no strict 'refs';
    my @bak;

    sub import {
        my ( $pkg, $to, $from ) = @_;
        my $subn = $to =~ /::/ ? $to : caller . "::$to";
        $to .= '::';
        push @bak,
          {
            pkg    => $to,
            symtab => \%$to,
            subn   => $subn,
            subr   => *$subn{CODE}
          };
        {
            no warnings;
            *$subn = sub () { $from }
        }
        $from .= '::';
        *$to = \%$from;
    }

    sub unimport {
        my $r = pop @bak;
        *{ $r->{pkg} } = $r->{symtab};
        if ( $r->{subr} ) {
            no warnings;
            *{ $r->{subn} } = $r->{subr};
        }
        else {
            my ( $ns, $f ) = $r->{subn} =~ /(.*::)(.*)/;
            delete $ns->{$f};
        }
    }
}

You use it like this:

{
    use alias FooBar => 'My::Really::Long::Module::FooBar';

    FooBar::do_something();
    FooBar->call_method();

    no alias;
}

It’s obviously alpha quality code. It doesn’t handle aliasing multiple packages (at least not cleanly), and I would want to write up tests to prove that it works in complex scenarios (even though I think it should). But a fascinating approach.

Tim King is Lead Developer at The Perl Shop. Tim got his start writing real-time embedded software for high-speed centrifuges the 1980’s and went on to do embedded software for Kurzweil Music Systems and Avid Technology. He has been developing for the web since the web existed, and brings discipline and skills honed from embedded systems to enterprise software. His expertise is in designing for software quality, achieved through automated code testing, test-first development, and risk managed refactoring, all through an agile process. This approach naturally lends itself to working with legacy code, such as successfully and safely refactoring a 465-line legacy function used in a video streaming application into a structurally sound design. Or designing for maintainability, through cleanly layered architectures, like a web service that can handle multiple RPC protocols using a common controller and a thin view layer, that can easily be supplemented to handle additional protocols. Tim is skilled in Perl, JavaScript, and other programming languages, in Internet protocols, in SQL, and is familiar with the internals of a variety of open source applications. Tim also writes and performs music, and has authored and published a number of inspirational books.

2 Replies to “Long Import Lists (and available strategies for managing them)”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.