unit module My::Show;
sub show (:$indent = False) is export {
if $indent {
say " Showing with indentation.";
} else {
say "Showing without indentation.";
}
}
When importing this code, I would like to specify any of these three:
use My::Show :indent;
use My::Show :!indent;
use My::Show;
Clarification (thanks @raiph): The :$indent
parameter of the show
sub has a default value. The user of the module should have the option to specify that default value on the use
line.
How to do so?
TL;DR You need to write and suitably invoke an EXPORT
subroutine.¹²³⁴⁵⁶⁷
used.rakumod
:
sub EXPORT ([:indent($default-indent) = False]) {
sub show (:$indent = $default-indent) is export { $indent }
Map.new: '&show' => &EXPORT::DEFAULT::show;
}
main.raku
:
use lib '.';
use used [:indent],;
say show; # True
used.rakumod
If a module⁹ is use
d, and it has an EXPORT
sub (at the compunit⁹ level), then:
The EXPORT
sub is called.
Any positional arguments provided with the use
statement are passed to the EXPORT
sub, and this can be used to achieve the effect you're after, within reason.⁴⁵⁶⁷
The used
module is nothing but an EXPORT
subroutine. It starts:
sub EXPORT ([:indent($default-indent) = False]) {
The signature and its behavior need explanation:
EXPORT
subs only bind to positional arguments.⁴
It's obviously desirable to be able to specify the default value for the named argument of show
by using what looks as close to passing that same named argument as possible. I'm mimicking that by writing [:indent]
in a use used [:indent],;
statement.⁴
Given that the passed positional argument is an array, the EXPORT
sub needs to deconstruct that positional argument to extract the inner argument inside the outer positional argument. That's what the [...]
does; it behaves as a sub-signature that "deconstructs" the positional array argument.
The deconstructing parameter :indent
is a named parameter, turning what we had to pass as an element of a positional array back into the form we want in the EXPORT
sub.
In addition, I take advantage of now having a true named parameter to also give the parameter an alias: :$default-index
. (You write aliased parameters by writing them in the form :foo(:bar(:$baz))
such that only the inner identifier/alias has a sigil, though the upshot is that you get $foo
, $bar
, and $baz
aliases.)
Specifying an alias tidies up a wrinkle that has to be addressed anyway: while my approach allows writing :indent
as part of the use
statement, there needs to be an alias for :$indent
because otherwise we can't make it the default value for the show
sub's :$indent
parameter:
sub show (:$indent = $default-indent) is export { $indent }
Hopefully you can see why it wouldn't work if I wrote :$indent = $indent
as the show
sub's signature.
The above may seem pretty complex. (Because it is.) I've written another run down in yet another footnote that might help.⁸
Finally there's:
Map.new: '&show' => &EXPORT::DEFAULT::show
I will rely on the relevant doc to guide you to understanding what you need to understand about that line. I suggest that you ignore it on a first read of this answer.
(In particular you may find the dual mentions of defaults -- $default-index
and DEFAULT
confusing. Suffice to say they've got nothing to do with each other.)
main.raku
All that's left is to use
the module⁹ and call show
.
Hopefully you know the use lib '.';
line in my main.raku
is just so the code works without me needing to explain (to someone cutting and pasting my code to run it) how to set up and use a library directory.
The next line is a use
statement that passes a positional argument, as previously discussed:
use used [:indent],;
Finally proof it works (though it's only very lightly tested!):
say show; # True
¹ I'm writing this answer with an air of authority, as if it was definitely useful and right. I find that helps make it easier to read. However, the reality is that, if my guess in my comment on your Q about what you wanted was wrong, this answer will likely be useless. And even if my guess was about right, this answer may still be useless, because what it deals with is close to the edge of my understanding and competence, and I may have stepped over the edge, and may even be wildly spinning my legs halfway across the cartoon canyon, without yet realizing I am.
² I'm just providing the simplest proof-of-concept I can think of to do what I think you want to be able to do, along with copious footnotes to cover caveats etc.
³ An EXPORT
sub is the most powerful of the standard mechanisms for controlling "exporting and selective importing". It may be a surprise I'm guiding you to use an export tool, especially a heavy weight one. What you want doesn't seem to be about exporting -- you've already got an is export
. Shouldn't that be enough? Nor does what you want seem to be about selective importing. You just want to import what the module had available for export, like normal, albeit declaring a parameter default for one of the exported sub
s. Can't be that difficult, right? Suffice to say, sub EXPORT
is the mechanism via which one can do the fanciest things that can be done during export/import, and, as I currently understand things, what you want to do requires sufficiently fancy footwork that indeed a sub EXPORT
is required.
⁴ In standard Raku, any pair (named argument) supplied at the top level of a use
statement (eg use module :indent;
) will be interpreted as an export/import "tag". (This explains why only positional parameters at the top level of the signature of EXPORT
subs can ever bind to anything.) Furthermore, the usual ways to stop Raku treating a pair as a named argument and instead treat it as a positional argument (eg by putting it in parens) don't work with a use
statement. Instead you have to do something like I did: putting the pair in an array literal ([:indent]
) plus adding an extra comma outside the array literal to put it into an outer list ([:indent],
)).⁵
⁵ You could just give up on the idea of passing a pair, and instead just pass a non-pair value eg. use module 99;
. (And modify the receiving EXPORT
sub to match.) But I can see why you wanted to use a pair whose name/key exactly matched the named parameter you wanted to specify the default for, so I've shown you what you have to do to make that work in ordinary circumstances.⁶
⁶ Raku is in principle an arbitrarily programmable language (with support for userland modification of Raku's syntax and/or semantics). So you could in principle create a module/pragma (or use an existing one if one existed) that changed Raku so that use
statements following use of the posited module/pragma would allow passing of top level named arguments to the EXPORT
sub. So then you could presumably write use module :indent;
, or something similar, instead of what I came up with. But I consider further discussion of such an approach is way beyond both the scope of a reasonable answer to your Q and my $paygrade.
⁷ The flip side to named arguments of a use
statement being export/import tags is that any positional arguments of a use
statement are passed to the use
d module's EXPORT
sub.
⁸ The arrangements I've made to be able to specify the default value for the show
sub's :$indent
parameter are surprisingly complex. Here's another run down as a refresher:
The use
statement use used [:indent],;
passes the positional argument [:indent]
to the EXPORT
sub.
The sub-signature in the EXPORT
routine's signature deconstructs the incoming array, extracting the :indent
pair and transforming the positional argument [:indent]
into the named parameter :$indent
.
The named parameter has an alias :$default-indent
. This is so it can be referred to as $default-indent
without the ambiguity of referring to it as $indent
. It has a default default value (False
) so that if no value is provided in a use
statement for the used
module, there's still a default value to pass on to the show
sub.
The default value for the named $indent
parameter of the show
sub is $default-indent
.
⁹ When I use the term "module" in this answer I don't (necessarily) mean a module
. A module
might be available as a "module" but what I mean by "module" in this answer is what a use
statement uses, namely a compunit.