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.

8 Comments

Lexical aliasing could actually be achieved with pure Perl.


use strict;
use warnings;
use feature 'say';
package A {
sub g {
say 'I am A::g.';
}
}
package a::b::c::d::e::f {
sub g {
say 'I am a::b::c::d::e::f::g.';
}
}
BEGIN {
my $bak;
*A:: = $bak;
BEGIN {
$bak = \%A::;
*A:: = \%a::b::c::d::e::f::;
}
A::g();
}
A::g();

A::g inside/outside the BEGIN block invoke different subroutines.

If the module uses Exporter::Tiny, then you'll get your "Export a hash of symbols" solution for free.

For example, List::MoreUtils uses Exporter::Tiny, so you can do this:

use v5.10;
use strictures;

my %LMU;
use List::MoreUtils { into => \%LMU }, -all;

# calls List::MoreUtils::uniq()
say for $LMU{uniq}->(qw/foo bar foo baz/);


That's an interesting technique, I also found out that it's a good way
to mess up with somebody:

use JSON::PP;
sub JSON::PP { 'lol' }
my $obj = JSON::PP->new;

This actually fails, so by declaring a subroutine named PP in the JSON
package, JSON::PP, a technically completely unrelated module becomes
dysfunctional.

Well, get back to the topic, it's good enough, I think I got it:

use strict;
use warnings;
use feature 'say';
sub A::g { say 'I am A::g.' }
sub a::b::c::d::e::f::g { say 'I am a::b::c::d::e::f::g.' }
sub B::g { say 'I am B::g.' }
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};
		}
	}
}
BEGIN { $INC{'alias.pm'} = 1 }
sub pcall { A::g() }
sub ocall { A->g() }
{ use alias 'A', 'a::b::c::d::e::f';
  { use alias 'A', 'B';
    # calls A::g, not B::g, so indeed lexical not dynamic.
    pcall();
    ocall();
    # B::g.
    A::g();
    A->g();
    no alias }
  # calls A::g, not a::b::c::d::e::f::g, so indeed lexical not dynamic.
  pcall();
  ocall();
  # a::b::c::d::e::f::g.
  A::g();
  A->g();
  no alias }
# A::g.
pcall();
ocall();

Unfortunately the code stopped working in recent versions of Perl...

Leave a comment

About Tim King

user-pic I've been working almost exclusively with Perl since 2006, and am one of the founding staff at The Perl Shop. I believe in designing systems that are easy to use, easy to understand, and easy to extend. I love software that does what you want, when you want it, without fighting you every step of the way.