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.
-
January 25, 2016 at 16:082016.1,2,3 What Are We Waiting 4? | Weekly changes in and around Perl 6