Home > Perl6 > A story of export and indirection

A story of export and indirection

While playing with Typesafe::XHTML::Writer I found myself wanting to allow the user of the module to provide an alternate class to handle the identity of typesafe HTML-fragments. Simply replacing Typesafe::HTML by uninstalling mine and installing their own would work. That sounds wrong and un-Perl6ish. To find a better way to do that I hit The Docs, found them lacking and tried to improve them. The result still wasn’t that nice. I simply lacked the knowledge to do it properly. Any lack of knowledge can be solved by the spec, that does define the whole process nicely but there are not that many test. Since Rakudo is test driven I had the suspicion that the untested stuff wouldn’t work. Three bug reports later I was sure that some parts of the spec are not implemented yet. There is no point in telling you what is missing, the bug reports are in the public domain. Instead I would like to share what I found working. If you are Japanese, Chinese or German, you will love the next part.

EXPORT All The Things

I’m not shouting at you, the function that is used to extend the default, trait driven way to export symbols, is called EXPORT. The capitals come from the fact that you are not ment to call that function. As any Phaser it’s called by Rakudo at the right time. This time, that time is at compile time when a use statement with a positional parameter is called. Any named parameters will not end up in the EXPORT function. Getting hold of the tags inside EXPORT is the first entry on my v6.d-wishlist. The return value of EXPORT must be a hash (that can be anonymous) with pairs of symbols with their respective sigils and a reference to the thing that is exported.

To provide the ability to change the type Typesafe::XHTML::Writer is returning I wanted to use type captures. Type captures work in Roles and Routines quite nicely.

sub foo(::T $t){ # return the same type we got fed
    dd T.^name;
    dd $t.WHAT.name;
}

my Int $i = 42;
foo($i)

# OUTPUT«"Int"␤Int␤»

When it comes to EXPORT things get complicated. Type captures take a type and store it for later use. When used in a thunk like a where clause or a anonymous sub, the later use is respected by Rakudo.

# in some module
sub EXPORT(::T = BaseClass) {
    { # anon Hash to be returned by &EXPORT
        '&consumer' => sub (T $c) {
            dd $c.^name
            T.new
        }
    }
}

#in some script
consumer(Int.new);

# OUTPUT«"Int"␤»

Multi subs can’t be anonymous, so the type capture would not be resolved inside EXPORT. We can store the type in a container and use it inside a where clause though. We lose introspection but typesafe it will be.

sub EXPORT(::T = BaseClass) {
    my $T = T;

    multi sub amulti($t where * ~~ $T) { say "$?FILE:$?LINE: amulti(T): ", $t.^name }
    multi sub amulti($t where * ~~ BaseClass) { say "$?FILE:$?LINE: amulti(BaseClass): ", $t.^name }
    { # anon Hash
        '&amulti' => &amulti,
    }
}

With the type stored in $T we can do any runtime type check we need and use it as an object factory. For my usecase that’s all I need. I can use a user defined class instead of my own until the type capture bugs are fixed. With any luck a :%s/$T/T/g is all I need to get rid of the workaround.

The complete example can be found in a gist.

Categories: Perl6