Perl module names are filepaths - and that's all

It’s common in Perl parlance to treat the words “module” and “package” as synonyms, and in practice they almost refer to the same thing. A module name is shorthand for a filepath, but a package name refers to a namespace within the Perl symbol table. It’s easy to forget this because module names and packages are written in the same colon-separated notation, and conventionally we give packages the same name as the module filepath. For example:

require Test::More; # load Test/More.pm

Test::More::ok 1; # call the ok function in the Test::More namespace

In this example, Test::More appears twice, but it really refers to two separate things; the first is a filepath, the second is a symbol namespace. They do not have to have the same name. Unfortunately perlmod perpetuates this myth:

A module is just a set of related functions in a library file, i.e., a Perl package with the same name as the file.

Demo

I’ll make a quick module called “ACME::Foo::Bar”, lib/ACME/Foo/Bar.pm looks like this:

package Whatever2;

our $VERSION = 0.01;

=head1 NAME

ACME::Foo::Bar - proof that module names and packages are not the same

=cut

sub me { __PACKAGE__ }

1;

Note that the package name Whatever2 is completely different to the module name ACME::Foo::Bar. At the terminal I can test it out:

$ perl -Ilib -MACME::Foo::Bar -E 'say Whatever2::me'
Whatever2

Perl happily loads the ACME::Foo::Bar module and the Whatever2 namespace (I originally used Whatever as the package name, but there is another package on CPAN with that name).

As a distribution

By adding a makefile, I can make this an installable distribution, Makefile.PL:

use 5.008000;

use ExtUtils::MakeMaker;
WriteMakefile(
  NAME           => 'ACME::Foo::Bar',
  VERSION_FROM   => 'lib/ACME/Foo/Bar.pm',
  ABSTRACT_FROM  => 'lib/ACME/Foo/Bar.pm',
  AUTHOR         => 'David Farrell',
  LICENSE        => 'perl5',
  MIN_PERL_VERSION => "5.008000",
);

Hell, I can add some tests while we’re at it, t/whatever.t:

#!/usr/bin/perl
use Test::More;

BEGIN { use_ok 'ACME::Foo::Bar', 'import module' }

is Whatever2::me, 'Whatever2', 'me() returns package name';

done_testing;

Installation is easy:

$ perl Makefile.PL
Generating a Unix-style Makefile
Writing Makefile for ACME::Foo::Bar
Writing MYMETA.yml and MYMETA.json
$ make
cp README.pod blib/lib/ACME/Foo/README.pod
cp lib/ACME/Foo/Bar.pm blib/lib/ACME/Foo/Bar.pm
Manifying 2 pod documents
$ make test
PERL_DL_NONLAZY=1 "/home/dfarrell/.plenv/versions/5.22.0/bin/perl5.22.0" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/whatever.t .. ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.02 CPU)
Result: PASS
$ make install
Manifying 2 pod documents
Installing /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/site_perl/5.22.0/ACME/Foo/Bar.pm
Installing /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/site_perl/5.22.0/ACME/Foo/README.pod
Installing /home/dfarrell/.plenv/versions/5.22.0/man/man3/ACME::Foo::README.3
Installing /home/dfarrell/.plenv/versions/5.22.0/man/man3/ACME::Foo::Bar.3
Appending installation info to /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/5.22.0/x86_64-linux/perllocal.pod

Now I can test the installed version at the terminal:

$ perl -MACME::Foo::Bar -E 'say Whatever2::me'
Whatever2

Tada! Works like a charm.

Toolchain issues

So now I have a distribution with a module containing a different package name, how well does it work with the CPAN toolchain? I’ve uploaded the distribution to CPAN, and you can view it on metacpan, and its CPAN Testers results are looking good.

There is one big issue though: the PAUSE indexer. PAUSE is the server which maintains CPAN data and its packages list is an index mapping package names to distributions. The indexer requires that a distribution has a module with a matching package name in it. This makes sense as it discourages users from uploading conflicting package names into different distributions.

CPAN clients lookup the package name in the packages list to know which distribution to install, so if my Whatever2 package isn’t in the list, I can’t install ACME::Foo::Bar that way:

$ cpan Whatever2
CPAN: Storable loaded ok (v2.53)
Reading '/home/dfarrell/.local/share/.cpan/Metadata'
  Database was generated on Thu, 15 Dec 2016 13:53:43 GMT
Warning: Cannot install Whatever2, don't know what it is.
Try the command

    i /Whatever2/

to find objects with matching identifiers.

But referencing it by its distribution name works fine:

$ cpan DFARRELL/ACME-Foo-Bar-0.02.tar.gz
--> Working on DFARRELL/ACME-Foo-Bar-0.02.tar.gz
Fetching http://www.cpan.org/authors/id/D/DF/DFARRELL/ACME-Foo-Bar-0.02.tar.gz ... OK
Configuring ACME-Foo-Bar-0.02 ... OK
Building and testing ACME-Foo-Bar-0.02 ... OK
Successfully installed ACME-Foo-Bar-0.02
1 distribution installed

One exception to this is cpanm, which falls back on a file search of the metacpan API if it doesn’t find the package in CPAN meta DB. So this works:

$ cpanm Whatever2

Summary

Neil Bowers has written an excellent glossary of CPAN terms. Packages with a namespace different to their module name are known as ‘cuckoo’ packages.

As conventions go, using the same package and module name is useful and recommended. Especially if the code is going to be shared via CPAN or otherwise. But it’s good to know that they’re not the same thing.


Updates:Changed example to use “require” instead of “use”, as “use” calls “import()” on the namespace. Changed the package name to “Whatever2” to avoid a CPAN conflict. Thanks to Perlancar, Aristotle and Grinnz for the feedback on /r/perl. 2016-12-15


This article was originally posted on PerlTricks.com.

Tags

David Farrell

David is a professional programmer who regularly tweets and blogs about code and the art of programming.

Browse their articles

Feedback

Something wrong with this article? Help us out by opening an issue or pull request on GitHub