Day 12 – Avoiding User Namespace Pollution with Perl 6 Modules

A bit of trickery is needed

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 { ... }
}

where each object to be exported is enclosed in a named export category block. That method has serious limitations (in addition to requiring another set of child blocks):

  • the objects cannot appear in more than one grouping and
  • the objects cannot be assigned other tags via the is export adjective on individual objects.

Consequently, to emulate Perl 5’s @EXPORT_OK array, each object would have to be wrapped in a unique block and could not have multiple tags. But then the docs say that all that ugly package exporting code can be replaced by this much simpler code for each item to be exported:

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 can also be helpful by adding other tags which will allow importing sets of objects (but such sets are not always so easily determined in practice).

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:

===SO6RRY!=== 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 :SUB) { say "bar" }
constant $bar is export(:bar-s :CONST) = 99;
constant %bar is export(:bar-h :CONST) = (a => 1);
constant @bar is export(:bar-a :CONST) = <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. We have also added an additional tag which groups the objects into the general categories of subroutines or constants so the user could import, say, just the constants as a set, e.g, use Foo :CONST;.

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, automatically-defined tag `:ALL:

use Foo :ALL;

but the author has provided users of the module complete flexibility in its application for them.

Conclusion

We now have a way to protect the user’s namespace by requiring him or her to selectively import objects by “name” (or perhaps other subsets of objects) unless the user chooses to import 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!

Merry Christmas! // Happy Holidays! I hope you will enjoy the gift of Perl 6 throughout the coming year!


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.

 

One thought on “Day 12 – Avoiding User Namespace Pollution with Perl 6 Modules

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s