Adjective Noun

Exportation Exploration - Update


I'm a little embarrassed, although I can't say I'm entirely surprised. In the last post, I made mention that I was possibly missing something obvious. I was right. In the Reddit comments, user FCO posted a gist (that's my untouched fork of FCO's work) that made me feel like an idiot. For all my aliasing experimentation, I'd totally overlooked the idea of declaring a function that does what EXPORT does and calling it... literally anything... and then exporting that function as EXPORT into the importers namespace.

Stripping the gist down to it's simplest form, is this snippet, which I have put up on the ecosystem.

multi sub exported-EXPORT(%exports, *@names --> Hash()) {
    do for @names -> $name {
        unless %exports{$name}:exists {
            die("Unknown name for export: '$name'");
        "&$name" => %exports{$name}

multi sub EXPORT {
    my %exports;
    multi sub trait_mod:<is>(Routine:D \r, Bool :$exportable!) is export {
        trait_mod:<is>(r, :exportable( => True));
    multi sub trait_mod:<is>(Routine:D \r, :$exportable!) is export {
        trait_mod:<is>(r, :export($exportable));
        %exports{} = r
        '&EXPORT' => sub (*@names) { exported-EXPORT %exports, |@names }

It's beautiful in it's simplicity. A function is declared that returns key/value pairs, where the keys are names, and the values are functions. For all intents and purposes, this will be the EXPORT sub that the importer uses. The trick is that it takes an additional hash as an argument. In this modules EXPORT sub, that very hash is declared, along side the exportable trait that populates that hash with functions that use it. Finally, the function that was initially declared is exported in to the module writers namespace as EXPORT. It is given the hash of exports, as well as whatever list is passed to it by module user in the use statement.

It also handles tags, hence the call to trait_mod:<is>(:export).

The snippet does not handle any aliasing, and I think outside of experimentation, that's a good idea. Imagine it handled import aliasing with the syntax described earlier, and people start using it. Then, later down the track, Rakudo adds an exportable (or something) trait that does the same thing, and because it's in core, people stop using my module. I'm cool with that, but if people who are using those modules are using some funky import aliasing trick, that might break someones code, and I'm not cool with that.

To be clear, I think aliasing is a useful feature, but the syntax should be standardised first. In the reddit comments, raiph pointed out that if you want your users to be able to alias imports, your exportable functions should be module scoped

use Exportable;
module Foo {
    our sub bar is exportable { ... }
    our sub baz is exportable { ... }

This allows functions to be imported by name, or called with the fully qualified name... and aliasing can be done with a simple assignment

use Foo 'bar';

sub baz { ... }

say bar();
say Foo::baz();

my &bazz := &Foo::baz;

say bazz();

In any event, the Exportable module is now available to use, and handles the syntax in the original article where I said "Ultimately what I'd like is something like this". Thanks again FCO.