Avoid User Namespace Pollution with Perl 6 Modules
As Perl 5 module authors, we were able to allow selective exporting easily by use of the @EXPORT_OK array where we listed all the objects that the user of our module could import by name, thereby giving the user full control over possible namespace clashes. However, in spite of all the new goodies provided by Perl 6, that feature is not yet available to us Perl 6 module authors. (For more information see issue #127305 on https://rt.perl.org.)
But the good news is there is a way to get the same effect, albeit with a bit more effort. The trick is in using the full power of tags with the export trait available with every object in a module.
One might get a little glassy-eyed when reading the Perl 6 docs concerning module import and export, of which a portion of code is shown here:
unit module MyModule;
my package EXPORT::DEFAULT {
our sub foo { ... }
}
my package EXPORT::other {
our sub bar { ... }
}
But then the docs say that all that ugly package exporting code can be replaced by this much simpler code:
sub foo is export { ... }
sub bar is export(:other) { ... }
And therein is our solution: We will use export tags to (1) allow the user to import only what he or she desires to import or (2) import everything. (The module author could be helpful by adding other tags which will allow importing sets of objects, but such sets are not always easily determined, so I will not discuss that further.)
Note one restriction before we continue: only valid identifier names are allowed in the tags, so we can’t use sigils or other special characters as we could in Perl 5.
For an example we use a simple, but unusual, Perl 6 module with multiple objects to be exported (and all have the same name, probably not a good practice, but handy for demonstration of the disambiguation problem):
unit module Foo;
sub bar() is export { say "bar" }
constant $bar is export = 99;
constant %bar is export = (a => 1);
constant @bar is export = <a b>;
We can then have access to all of those objects by merely using the module in a program file:
use Foo;
bar;
say $bar;
say %bar;
say @bar;
Executing it yields:
bar
99
{a => 1}
[a b]
as expected. Now let’s modify the module by adding an export tag to the bar routine:
sub bar() is export(:bar) { say "bar" }
and then, when our program is executed, we get:
===SORRY!=== Error while compiling...
Undeclared routine:
bar used at line 7. Did you mean 'VAR', 'bag'?
so we modify our use line in the program to:
use Foo :bar;
and execute the program again to get:
===SORRY!=== Error while compiling...
Variable '$bar' is not declared. Did you mean '&bar'?
It seems that as soon as we, the users, restrict the use statement with a tag, the non-tagged objects are not available! Now we, the authors, have two choices:
- tag all objects with the same tag or
- tag them with separate tags.
If we tag them the same, then all will be available—which is probably not a problem. However, that would defeat our goal of having unique tags.
If we name them with unique tags, we will need some way to distinguish them (remember, we can’t use their sigils), which leads us to a possible convention we show here in the final module:
unit module Foo;
sub bar() is export(:bar) { say "bar" }
constant $bar is export(:bar-s) = 99;
constant %bar is export(:bar-h) = (a => 1);
constant @bar is export(:bar-a) = <a b>;
Notice the suffixes on the non-subroutine objects (all constants) have their kind indicated by the ‘-X’ where the ‘X’ is ‘s’ for scalar, ‘h’ for hash, and ‘a’ for array. Now we just change the use line to
use Foo :bar, :bar-s, :bar-h, :bar-a;
and get
bar
99
{a => 1}
[a b]
again. Thus the good news is we can add a tag to the export trait that is the same as the name of the subroutine, but the sad news is we can’t use the appropriate sigil as in Perl 5 to disambiguate among objects with the same name. The solution to that is to use some convention as demonstrated above (akin to the ugly Hungarian notation in C) that will have the same effect.
Of course in this somewhat-contrived example, the user could have imported all the objects at once by using the special tag :ALL:
use Foo :ALL;
In summary, we have a way to protect the user by requiring him or her to selectively import objects by “name” unless the user chooses importing everything. The only downsides I see are:
- the extra effort required by the module author to explicitly tag all exportable objects and
- the restriction on selecting an appropriate tag for different objects with the same name.
I love Perl 6, and the old Perl motto TIMTOWTDI still holds true as we’ve just seen!
Note: Thanks to Moritz Lenz (IRC #perl6: user moritz) for constructive comments about the bad practice of exporting variables—hence the example now exports constants instead.
Leave a comment