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 aUNIT
scoped package in theEXPORT
namespace. For example,is export(:FOO)
will add the target to theUNIT::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.