Sterling has too many projects

Blogging about Perl 6 programming, microcontrollers & electronics, 3D printing, and whatever else...

What does the export trait actually do?

I started this post as a rehash of Modules with some additional details. However, as I started running my examples, I found out that while the documentation on modules is good, it does not tell the full story regarding exports. As I do not want to write a manifesto on module exports, I’m going to assume you already read the above document and understand Perl 6 exports. If not, go read it and I’ll wait for you to return.

Ready? Okay, let’s go.

First, let me explain why I’m on this odyssey: I am writing a module, let’s call it Prometheus::Client, and that module really needs to export some symbols to be useful. However, due to how I’ve structured things, I would might prefer that the symbols I export actually be located in other compilation units, for example, the file declaring the Prometheus::Client::Metrics module. That means I need a way to re-export the exports of another module. I’ve done this before on a small scale for some things, but this is going to be much expanded. I wanted to make sure I knew what Perl 6 was doing before I started. In the process I discovered that the exports rabbit hole is deeper than I’d originally thought.

Let’s start with this simple statement from the Modules documentation I mentioned above:

Beneath the surface, is export is adding the symbols to a UNIT scoped package in the EXPORT namespace. For example, is export(:FOO) will add the target to the UNIT::EXPORT::FOO package. This is what Perl 6 is really using to decide what to import.

This is followed by the claim that this code:

unit module MyModule;
 
sub foo is export { ... }
sub bar is export(:other) { ... }

Is the same as:

unit module MyModule;
 
my package EXPORT::DEFAULT {
    our sub foo { ... }
}
 
my package EXPORT::other {
    our sub bar { ... }
}

If I were a “fact checker” I’d have to rate the quote “is the same as” regarding these two code snippets as “Half True” at best. It does, in fact, create these packages. However, that is not all it does.

This becomes clear if you understand the implications of the Introspection section of that same document. There it shows code like this:

use URI::Escape;
say URI::Escape::EXPORT::.keys;
# OUTPUT: «(DEFAULT ALL)␤»

And this:

say URI::Escape::EXPORT::DEFAULT::.keys;
# OUTPUT: «(&uri-escape &uri-unescape &uri_escape &uri_unescape)␤» 

If you aren’t careful, you won’t see it. I didn’t until I started playing around with code to see what works. Those is export lines in that MyModule example above do not only create the UNIT scoped package. They also create an OUR scoped package inside the current package namespace that can be used for introspection.

So, if you really want to replicate what is export does internally when it calls Rakudo::Interals.EXPORT_SYMBOL, you will have to do something like this for the complete MyModule-without-is export implementation:

unit module MyModule;

my package EXPORT::DEFAULT {
    our sub foo { ... }
}

my package EXPORT::other {
    our sub bar { ... }
}

{
    # Create a package object to be MyModule::EXPORT
    my $export = Metamodel::PackageHOW.new_type(:name('EXPORT'));
    $export.^compose;

    # Create a package object to be MyModule::EXPORT::DEFAULT
    my $default = Metamodel::PackageHOW.new_type(:name('DEFAULT'));
    $default.^compose;

    # Add the &foo symbol to the introspection package
    $default.WHO<&foo> := &UNIT::EXPORT::DEFAULT::foo;

    # Create a package object to be MyModule::EXPORT::other
    my $other = Metamodel::PackageHOW.new_type(:name('other'));
    $other.^compose;

    # Add the &bar symbol to the introspection package
    $other.WHO<&bar> := &UNIT::EXPORT::other::bar;

    # Add DEFAULT and other in EXPORT
    $export.WHO<DEFAULT> := $default;
    $export.WHO<other> := $other;

    # Add EXPORT in MyModule
    MyModule.WHO<EXPORT> := $export;
}

I haven’t yet found a cleaner way to do all that extra stuff at the bottom without doing all this introspective symbol table munging. However, code like this will get you pretty close to what is export does internally. I also haven’t even delved into what an EXPORT sub does by comparison. I’ll save that for another time.

I should also mention that I came across a module in the Perl 6 ecosystem named CompUnit::Util which might be useful to me on my Prometheus::Client problems and maybe even for setting up the two EXPORT modules too. However, I haven’t really dived into that any farther than noting the age of the code and that it makes use of undocumented-but-roasted methods of Stash to do whatever it does. I may look at it later when I’m less tired. Or maybe I will just decide to do what my new library in a completely different way. Whatev'.

Cheers.