In another question (How can I declare and use a Perl 6 module in the same file as the program?), I had code like this:
module Foo {
sub foo ( Int:D $number ) is export {
say "In Foo";
}
}
foo( 137 );
I wanted to inspect the Foo module to see if it's defined and what might be in it to do a bit of debugging. Since Foo is a module and not a class, do the meta-methods make sense?
Also, I thought there used to be a way to get a list of methods in a class. I'd like to get a list of subroutines in a module. That would be one way to test that I'd defined the right stuff and Perl 6 knows about them. In my Perl 5 stuff, I often test that I've defined a subroutine because I had a period where I'd choose a name in the module and a slightly different name in the tests (like last night, I guess, with valid_value and is_value_value). If I could test that foo is defined, I could do a bit of debugging here.
You can get at a packages' symbol table by adding a trailing :: to its name. This will work for modules and classes both, but in case of classes will not include any methods, as those are associated with the type object and not the package itself.
The symbol table is of type Stash, which is associative (ie supports hash-like operations):
module Foo {
sub foo is export { ... }
sub bar is export(:bar) { ... }
our sub baz { ... }
}
# inspect the symbol table
say Foo::.WHAT; #=> (Stash)
say Foo::.keys; #=> (EXPORT &baz)
say Foo::<&baz>.WHAT; #=> (Sub)
# figure out what's being exported
say Foo::EXPORT::.keys; #=> (bar DEFAULT ALL)
say Foo::EXPORT::bar::.keys; #=> (&bar)
say Foo::EXPORT::DEFAULT::.keys; #=> (&foo)
say Foo::EXPORT::ALL::.keys; #=> (&bar &foo)
Related
An example on the 'class Signature' page appears as:
sub named(:official($private)) { "Official business!" if $private }; named :official;
<-----
Note: That's where to example ends ... there is no output described or shown.
So I typed in the code and when the method 'named' is executed it always prints "Official business!" no matter the value of $private. That is, if $private=True then invoking 'named' prints "Official business!" as you might expect but with $private=False invoking 'named' still prints 'Official business!' whereas I thought there should be no output generated. I'm missing something aren't I but what?
There are a couple of things going on in this short example:
sub named(:official($private)) { "Official business!" if $private }
-------- --------
| |
| > '$private' is the name of this var within the sub
|
> 'official' is the (sigilless) name of this var outside
------------------
|
> ':official($private)' is the
raku Pair 'official => $private'
named :official
----- ---------
| |
| > the raku Pair 'official => True' (so $private ~~ True)
|
> the same as 'named( official => True );'
So when you try this in the repl...
> named :official; #Official business!
> named( official => True ) #Official business! [same thing]
-or-
> named :!official #()
> named( official => False ) #() [same thing]
At first encounter, in this example, raku is quite quirky. Why?
the raku Pair literal syntax makes it easy to handle common function argument cases such as :dothis but :!notthat adverbs
the :name(value) formulation is re-used here to map external to internal var names - nothing special needed since the Pair syntax already does it - but super useful for code encapsulation
similar situation with argument aliases ... you can think of this as Pairs of Pairs ... again, no special syntax needed since just using the built in Pair literal syntax
So, imho, raku is uniquely cool in that we just have to learn some basic elemental syntax (in this case, the Pair literals) and then they can be applied consistently in many ways to concisely solve quite thorny coding scenarios.
I see how the doc example was confusing. Here's an expanded version of that explanation:
sub named(:official($private)) {
# Inside the function, the argument the caller passed in
# is bound to the variable $private. Nothing is bound to official.
say $private;
}
# Outside the function, the caller doesn't need to care what term the
# function uses. All it needs to know is the name of the parameter – :official
named(:official('foo')); # OUTPUT: «foo»
This is especially handy when the function is in a different module – the owner of the function can refactor the name of the their $private argument without breaking any external callers. And, if they think of a better name for external callers to use, they can even add that without breaking backwards compatibility: sub named(:official(:better-name(:$private))) { … } lets callers use either :official or :better-name as the parameter name, without having any impact on the variable name inside the function.
Does that make more sense?
Oh, and one note regarding this:
if $private=True then invoking 'named' prints "Official business!" as you might expect
If I'm understanding you correctly, you mean writing something like
sub named(:official($private)=True) { "Official business!" if $private }
Is that what you meant? If so, you should know that $private=True in that signature isn't really setting $private to anything – it's just providing a default value for the variable $private. So you can get this output:
sub named(:official($private)=True) {
say $private;
}
named(); # OUTPUT: «True»
named(:official(42)); # OUTPUT: «42»
In any event, I'll send in a PR to the docs repo and try to clarify the named argument examples a bit. Hope you enjoy Raku!
Via pretzel logic I've concluded that:
You may have carefully written your Q to trick us, and you will reveal the truth if we correctly solve the riddle.
Or (a tad more likely perhaps... 🤡):
Adopting that mindset will help us all -- you, readers, answerers -- have fun as we try to solve the riddle inherent in your sincere Q even if you weren't planning to make this fun...
Either way, please read this answer and let me know if it contributes to solving the riddle. TIA. :)
I'm missing something aren't I but what?
Perhaps this is a key for solving the riddle: we're supposed to ask ourselves "What has Keith deliberately missed out of the Q?".
Of course, if this is so, the first thing you've omitted is that it's a riddle. In which case, major kudos! And if it's not an intentional riddle, well, it's still a riddle, so kudos anyway, and let's try solve it!
#codesection's answer points at perhaps the strongest clue. While you didn't write a minimal reproducible example, you did write the sub definition code, and you did say you'd written $private=False.
So let's presume you literally wrote that sub definition and you literally wrote $private=False.
Furthermore, that:
with $private=False invoking 'named' still prints 'Official business!' whereas I thought there should be no output generated.
means there was some output generated in all cases.
Anyhoo, how can all of these things be true at the same time?
These are compile-time errors:
sub named( :official( $private = True ) ) { "Official business!" if $private }
sub named( :official( $private = False ) ) { "Official business!" if $private }
So is this:
$private = False;
unless there was a prior my $private....
And this:
sub named( :official( $private ) ) {
$private = True;
"Official business!" if $private
}
named;
yields Cannot assign to a readonly variable or a value (currently at runtime) because, by default, parameters to routines are readonly.
You also said there was output (ostensibly Official business!, but perhaps sometimes something else, eg ()?).
You didn't mention a say or similar routine for displaying output. How could that be? Let's introduce a say.
So, putting these things together, and applying pretzel logic, I arrived here:
sub named(:official($private)) { "Official business!" if $private }
my $private;
$private = False;
say named :official; # Official business!
say named :!official; # ()
say named official => False; # ()
say named :official(False); # ()
say named; # ()
An interpretation of this as solving the riddle:
() is not "no output". So it doesn't contradict your Q narrative.
We've written $private = False. It has no impact on the code, because of lexical scoping, but we did write it. :)
Am I warm?
Dear kind and generous raku folk,
Thank you for the replies and discourse. After which I now understand what I didn't understand. And the root of my misunderstanding started here: At the time of writing this, the doc page for class Pair says:
There are many syntaxes for creating Pairs
and goes on to give examples, one of which is this line:
:foo(127) # short for foo => 127
So I thought in the raku interactive environment that these two lines:
> :foo(127)
foo => 127
> :foo.kv
(foo True)
Would answer (foo 127) for the last line. However sadly, no, the answer is (foo True)
But I didn't know that when I went off to read the 'Class Signature' page and got into a pickle with the example:
sub named(:official($private));
named :official
Which now appears to be a truncated example.
And sent me off on a tangent thinking there was lot of magic in what a colon can do.
Anyway, I hope everyone is keeping well ... see you later alligators.
I have 4 files all in the same directory: main.rakumod, infix_ops.rakumod, prefix_ops.rakumod and script.raku:
main module has a class definition (class A)
*_ops modules have some operator routine definitions to write, e.g., $a1 + $a2 in an overloaded way.
script.raku tries to instantaniate A object(s) and use those user-defined operators.
Why 3 files not 1? Since class definition might be long and separating overloaded operator definitions in files seemed like a good idea for writing tidier code (easier to manage).
e.g.,
# main.rakumod
class A {
has $.x is rw;
}
# prefix_ops.rakumod
use lib ".";
use main;
multi prefix:<++>(A:D $obj) {
++$obj.x;
$obj;
}
and similar routines in infix_ops.rakumod. Now, in script.raku, my aim is to import main module only and see the overloaded operators also available:
# script.raku
use lib ".";
use main;
my $a = A.new(x => -1);
++$a;
but it naturally doesn't see ++ multi for A objects because main.rakumod doesn't know the *_ops.rakumod files as it stands. Is there a way I can achieve this? If I use prefix_ops in main.rakumod, it says 'use lib' may not be pre-compiled perhaps because of circular dependentness
it says 'use lib' may not be pre-compiled
The word "may" is ambiguous. Actually it cannot be precompiled.
The message would be better if it said something to the effect of "Don't put use lib in a module."
This has now been fixed per #codesections++'s comment below.
perhaps because of circular dependentness
No. use lib can only be used by the main program file, the one directly run by Rakudo.
Is there a way I can achieve this?
Here's one way.
We introduce a new file that's used by the other packages to eliminate the circularity. So now we have four files (I've rationalized the naming to stick to A or variants of it for the packages that contribute to the type A):
A-sawn.rakumod that's a role or class or similar:
unit role A-sawn;
Other packages that are to be separated out into their own files use the new "sawn" package and does or is it as appropriate:
use A-sawn;
unit class A-Ops does A-sawn;
multi prefix:<++>(A-sawn:D $obj) is export { ++($obj.x) }
multi postfix:<++>(A-sawn:D $obj) is export { ($obj.x)++ }
The A.rakumod file for the A type does the same thing. It also uses whatever other packages are to be pulled into the same A namespace; this will import symbols from it according to Raku's standard importing rules. And then relevant symbols are explicitly exported:
use A-sawn;
use A-Ops;
sub EXPORT { Map.new: OUTER:: .grep: /'fix:<'/ }
unit class A does A-sawn;
has $.x is rw;
Finally, with this setup in place, the main program can just use A;:
use lib '.';
use A;
my $a = A.new(x => -1);
say $a++; # A.new(x => -1)
say ++$a; # A.new(x => 1)
say ++$a; # A.new(x => 2)
The two main things here are:
Introducing an (empty) A-sawn package
This type eliminates circularity using the technique shown in #codesection's answer to Best Way to Resolve Circular Module Loading.
Raku culture has a fun generic term/meme for techniques that cut through circular problems: "circular saws". So I've used a -sawn suffix of the "sawn" typename as a convention when using this technique.[1]
Importing symbols into a package and then re-exporting them
This is done via sub EXPORT { Map.new: ... }.[2] See the doc for sub EXPORT.
The Map must contain a list of symbols (Pairs). For this case I've grepped through keys from the OUTER:: pseudopackage that refers to the symbol table of the lexical scope immediately outside the sub EXPORT the OUTER:: appears in. This is of course the lexical scope into which some symbols (for operators) have just been imported by the use Ops; statement. I then grep that symbol table for keys containing fix:<; this will catch all symbol keys with that string in their name (so infix:<..., prefix:<... etc.). Alter this code as needed to suit your needs.[3]
Footnotes
[1] As things stands this technique means coming up with a new name that's different from the one used by the consumer of the new type, one that won't conflict with any other packages. This suggests a suffix. I think -sawn is a reasonable choice for an unusual and distinctive and mnemonic suffix. That said, I imagine someone will eventually package this process up into a new language construct that does the work behind the scenes, generating the name and automating away the manual changes one has to make to packages with the shown technique.
[2] A critically important point is that, if a sub EXPORT is to do what you want, it must be placed outside the package definition to which it applies. And that in turn means it must be before a unit package declaration. And that in turn means any use statement relied on by that sub EXPORT must appear within the same or outer lexical scope. (This is explained in the doc but I think it bears summarizing here to try head off much head scratching because there's no error message if it's in the wrong place.)
[3] As with the circularity saw aspect discussed in footnote 1, I imagine someone will also eventually package up this import-and-export mechanism into a new construct, or, perhaps even better, an enhancement of Raku's built in use statement.
Hi #hanselmann here is how I would write this (in 3 files / same dir):
Define my class(es):
# MyClass.rakumod
unit module MyClass;
class A is export {
has $.x is rw;
}
Define my operators:
# Prefix_Ops.rakumod
unit module Prefix_Ops;
use MyClass;
multi prefix:<++>(A:D $obj) is export {
++$obj.x;
$obj;
}
Run my code:
# script.raku
use lib ".";
use MyClass;
use Prefix_Ops;
my $a = A.new(x => -1);
++$a;
say $a.x; #0
Taking my cue from the Module docs there are a couple of things I am doing different:
Avoiding the use of main (or Main, or MAIN) --- I am wary that MAIN is a reserved name and just want to keep clear of engaging any of that (cool) machinery
Bringing in the unit module declaration at the top of each 'rakumod' file ... it may be possible to use bare files in Raku ... but I have never tried this and would say that it is not obvious from the docs that it is even possible, or supported
Now since I wanted this to work first time you will note that I use the same file name and module name ... again it may be possible to do that differently (multiple modules in one file and so on) ... but I have not tried that either
Using the 'is export' trait where I want my script to be able to use these definitions ... as you will know from close study of the docs ;-) is that each module has it's own namespace (the "stash") and we need export to shove the exported definitions into the namespace of the script
As #raiph mentions you only need the script to define the module library location
Since you want your prefix multi to "know" about class A then you also need to use MyClass in the Prefix_Ops module
Anyway, all-in-all, I think that the raku module system exemplifies the unique combination of "easy things easy and hard thinks doable" ... all I had to do with your code (which was very close) was tweak a few filenames and sprinkle in some concise concepts like 'unit module' and 'is export' and it really does not look much different since raku keeps all the import/export machinery under the surface like the swan gliding over the river...
As a follow up to this question about using different APIs in a single program, Liz Mattijsen suggested to use constants. Now here's a different use case: let's try to create a multi that differentiates by API version, like this:
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
multi sub get-api( WithApi $foo where .^api() == 1 ) {
return "That's version 1";
}
multi sub get-api( WithApi $foo where .^api() == 2 ) {
return "That's version deuce";
}
say get-api(WithApi.new);
say two.new.^api;
say get-api(two.new);
We use a constant for the second version, since both can't be together in a single symbol space. But this yields this error:
That's version 1
2
Cannot resolve caller get-api(WithApi.new); none of these signatures match:
(WithApi $foo where { ... })
(WithApi $foo where { ... })
in block <unit> at ./version-signature.p6 line 18
So say two.new.^api; returns the correct api version, the caller is get-api(WithApi.new), so $foo has the correct type and the correct API version, yet the multi is not called? Is there something I'm missing here?
TL;DR JJ's answer is a run-time where clause that calls a pair of methods on the argument of concern. Everyone else's answers do the same job, but using compile-time constructs that provide better checking and much better performance. This answer blends my take with Liz's and Brad's.
Key strengths and weaknesses of JJ's answer
In JJ's answer, all the logic is self-contained within a where clause. This is its sole strength relative to the solution in everyone else's answers; it adds no LoC at all.
JJ's solution comes with two significant weaknesses:
Checking and dispatch overhead for a where clause on a parameter is incurred at run-time1. This is costly, even if the predicate isn't. In JJ's solution the predicates are costly ones, making matters even worse. And to cap it all off, the overhead in the worse case when using multiple dispatch is the sum of all the where clauses used in all the multis.
In the code where .^api() == 1 && .^name eq "WithApi", 42 of the 43 characters are duplicated for each multi variant. In contrast a non-where clause type constraint is much shorter and would not bury the difference. Of course, JJ could declare subsets to have a similar effect, but then that would eliminate the sole strength of their solution without fixing its most significant weakness.
Attaching compile-time metadata; using it in multiple dispatch
Before getting to JJ's problem in particular, here are a couple variations on the general technique:
role Fruit {} # Declare metadata `Fruit`
my $vegetable-A = 'cabbage';
my $vegetable-B = 'tomato' does Fruit; # Attach metadata to a value
multi pick (Fruit $produce) { $produce } # Dispatch based on metadata
say pick $vegetable-B; # tomato
Same again, but parameterized:
enum Field < Math English > ;
role Teacher[Field] {} # Declare parameterizable metadata `Teacher`
my $Ms-England = 'Ms England';
my $Mr-Matthews = 'Mr Matthews';
$Ms-England does Teacher[Math];
$Mr-Matthews does Teacher[English];
multi field (Teacher[Math]) { Math }
multi field (Teacher[English]) { English }
say field $Mr-Matthews; # English
I used a role to serve as the metadata, but that's incidental. The point was to have metadata that can be attached at compile-time, and which has a type name so dispatch resolution candidates can be established at compile-time.
A compile-time metadata version of JJ's run-time answer
The solution is to declare metadata and attach it to JJ's classes as appropriate.
A variation on Brad's solution:
class WithApi1 {}
class WithApi2 {}
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> is WithApi1 {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi2 {}
constant three = anon class WithApi:ver<0.0.2>:api<1> is WithApi1 {}
multi sub get-api( WithApi1 $foo ) { "That's api 1" }
multi sub get-api( WithApi2 $foo ) { "That's api deuce" }
say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1
An alternative is to write a single parameterizable metadata item:
role Api[Version $] {}
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> does Api[v1] {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> does Api[v2] {}
constant three = anon class WithApi:ver<0.0.2>:api<v1> does Api[v1] {}
multi sub get-api( Api[v1] $foo ) { "That's api 1" }
multi sub get-api( Api[v2] $foo ) { "That's api deuce" }
say get-api(one.new); # That's api 1
say get-api(two.new); # That's api deuce
say get-api(three.new); # That's api 1
Matching ranges of versions
In a comment below JJ wrote:
If you use where clauses you can have multis that dispatch on versions up to a number (so no need to create one for every version)
The role solution covered in this answer can also dispatch on version ranges by adding another role:
role Api[Range $ where { .min & .max ~~ Version }] {}
...
multi sub get-api( Api[v1..v3] $foo ) { "That's api 1 thru 3" }
#multi sub get-api( Api[v2] $foo ) { "That's api deuce" }
This displays That's api 1 thru 3 for all three calls. If the second multi is uncommented it takes precedence for v2 calls.
Note that the get-api routine dispatch is still checked and candidate resolved at compile-time despite the fact the role signature includes a where clause. This is because the run-time for running the role's where clause is during compilation of the get-api routine; when the get-api routine is called the role's where clause is no longer relevant.
Footnotes
1 In Multiple Constraints, Larry wrote:
For 6.0.0 ... any structure type information inferable from the where clause will be ignored [at compile-time]
But for the future he conjectured:
my enum Day ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
Int $n where 1 <= * <= 5 # Int plus dynamic where
Day $n where 1 <= * <= 5 # 1..5
The first where is considered dynamic not because of the nature of the comparisons but because Int is not finitely enumerable. [The second constraint] ... can calculate the set membership at compile time because it is based on the Day enum, and hence [the constraint, including the where clause] is considered static despite the use of a where.
The solution is really simple: also alias the "1" version:
my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
multi sub get-api(one $foo) {
return "That's version 1";
}
multi sub get-api(two $foo) {
return "That's version deuce";
}
say one.new.^api; # 1
say get-api(one.new); # That's version 1
say two.new.^api; # 2
say get-api(two.new); # That's version deuce
And that also allows you to get rid of the where clause in the signatures.
Mind you, you won't be able to distinguish them by their given name:
say one.^name; # WithApi
say two.^name; # WithApi
If you want to be able to do that, you will have to set the name of the meta-object associated with the class:
my constant one = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
BEGIN one.^set_name("one");
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
BEGIN two.^set_name("two");
Then you will be able to distinguish by name:
say one.^name; # one
say two.^name; # two
Only one thing can be in a given namespace.
I assume the whole reason you are putting the second declaration into a constant and declaring it with my is that it was giving you a redeclaration error.
The thing is, that it should still be giving you a redeclaration error.
Your code shouldn't even compile.
You should have to declare the second one with anon instead.
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
It would then be obvious why what you are trying to do doesn't work.
The second declaration is never installed into the namespace in the first place.
So when you use it in the second multi sub it is declaring that its argument is the same type as the first class.
(Even when you are using my in your code it isn't managing to install it into the namespace.)
You are assuming that the namespace is a flat namespace.
It isn't.
You can have a class that has one name, but is only ever accessible under another.
our constant Bar = anon class Foo {}
sub example ( Bar $foo ) {
say $foo.^name; # Foo
}
example( Bar );
Raku installs the class into the namespace for you as a convenience.
Otherwise there would be a lot of code that looked like:
our constant Baz = class Baz {}
You are trying to use the namespace while at the same time trying to subvert the namespace.
I don't know why you expect that to work.
A quick way to get your exact code to work as you wrote it, is to declare that the second class is a subclass of the first.
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> is WithApi {}
# ^________^
Then when the second multi checks that its argument is of the first type, it still matches when you give it the second.
This isn't great.
There isn't really a built-in way to do exactly what you want.
You could try to create a new meta type that can create a new type that will act like both classes.
I personally would just alias them both to independent names.
constant one = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
constant two = anon class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
If you are loading them from modules:
constant one = BEGIN {
# this is contained within this block
use WithApi:ver<0.0.1>:auth<github:JJ>:api<1>;
WithApi # return the class from the block
}
constant two = BEGIN {
use WithApi:ver<0.0.1>:auth<github:JJ>:api<2>;
WithApi
}
Elizabeth Mattijsen answer above game me a hint. Signatures match symbol, not symbol name. However, when you alias (using a constant) to a new name, you still keep the name. Let's use this to have an uniform multi call where the only thing that changes is the api version:
class WithApi:ver<0.0.1>:auth<github:JJ>:api<1> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
my constant two = my class WithApi:ver<0.0.1>:auth<github:JJ>:api<2> {}
my constant three = my class WithApi:ver<0.0.2>:api<1> {}
multi sub get-api( $foo where .^api() == 1 && .^name eq "WithApi" ) {
return "That's version 1";
}
multi sub get-api( $foo where .^api() == 2 && .^name eq "WithApi") {
return "That's version deuce";
}
say get-api(WithApi.new); # That's version 1
say get-api(two.new); # That's version deuce
say get-api(three.new); # # That's version 1
Again following Elizabeth's answer in the previous question, constants are used for the new versions to avoid namespace clashes, but the multis will be selected solely based in api version in a relatively type-safe way, without needing to use the aliased symbols in the signature. Even if you invent a new constant to alias WithApi with any metadata, the multi will still be selected based on api version (which is what I was looking for).
In Perl 5, a module can declare an AUTOLOAD() subroutine that will be called if an undefined subroutine is called. Similarly in Perl 6 classes there is a FALLBACK() method. But I could not find anything similar to FALLBACK() for modules.
Here is an example of a use case: I would like to write a new module MyModule that exports all subroutines that another module OtherModule exports, and then forwards all subroutine calls to those exported methods (not yet defined) in MyModule to the corresponding subroutine in OtherModule. I think I can get the exported symbols from OtherModule using CompUnit::Util.
Question: How can I implement the Perl 5 autoload mechanism in Perl 6?
Lexical names are resolved statically, so I'm not sure how to implement AUTOLOAD without some deep magic.
Regarding your specific example, I'm also not quite sure what the 'proper' way to do so would be, but here's a 'dirty' solution that seems to work, replacing a sub &foo exported by a module Foo:
# get all subroutines exported by Foo by default
BEGIN my #reex = do {
use Foo;
MY::.pairs.grep(*.key.starts-with('&'));
}
# export our replacement subroutine (also in the default namespace)
sub foo is export {
say "new foo";
}
# re-export subroutines lacking a replacement
# placed at the end of the module so EXPORT will have been populated
BEGIN EXPORT::DEFAULT::{.key} //= .value
for #reex;
I want to use Raku Modules to group some functions, I often use. Because these functions are all loosely coupled, I don't like to add them in a class.
I like the idea of use, where you can select, which functions should be imported, but I don't like it, that the functions, which are imported are then stored in the global namespace.
For example if I have a file my_util.pm6:
#content of my_util.pm6
unit module my_util;
our sub greet($who) is export(:greet) {
say $who;
}
sub greet2($who) is export(:greet2) {
say $who;
}
sub greet3($who) is export(:greet3) {
say $who;
}
and a file test.p6:
#!/usr/bin/perl6
#content of test.p6
use v6.c;
use lib '.';
use my_util :greet2;
greet("Bob"); #should not work (because no namespace given) and also doesn't work
greet2("Bob"); #should not work (because no namespace given) but actually works
greet3("Bob"); #should not work (because no namespace given) and also doesn't work
my_util::greet("Alice"); #works, but should not work (because it is not imported)
my_util::greet2("Alice"); #should work, but doesn't work
my_util::greet3("Alice"); #should not work (because it is not imported) and also doesn't work
I would like to call all functions via my_util::greet() and not via greet() only.
The function greet() defined in my_util.pm6 comes very close to my requirements, but because it is defined as our, it is always imported. What I like is the possibility, to select which functions should be imported and it should be possible to leave it in the namespace defined by the module (i.e. it doesn't pollute the global namespace)
Does anyone know, how I can achieve this?
To clear up some potential confusion...
Lexical scopes and package symbol tables are different things.
my adds a symbol to the current lexical scope.
our adds a symbol to the current lexical scope, and to the public symbol table of the current package.
use copies the requested symbols into the current lexical scope.
That's called "importing".
The :: separator does a package lookup – i.e. foo::greet looks up the symbol greet in the public symbol table of package foo.
This doesn't involve any "importing".
As for what you want to achieve...
The public symbol table of a package is the same no matter where it is referenced from... There is no mechanism for making individual symbols in it visible from different scopes.
You could make the colons part of the actual names of the subroutines...
sub foo::greet($who) is export(:greet) { say "Hello, $who!" }
# This subroutine is now literally called "foo::greet".
...but then you can't call it in the normal way anymore (because the parser would interpret that as rule 4 above), so you would have to use the clunky "indirect lexical lookup" syntax, which is obviously not what you want:
foo::greet "Sam"; # Could not find symbol '&greet'
::<&foo::greet>( "Sam" ); # Hello, Sam!
So, your best bet would be to either...
Declare the subroutines with our, and live with the fact that all of them can be accessed from all scopes that use the module.
Or:
Add the common prefix directly to the subroutine names, but using an unproblematic separator (such as the dash), and then import them normally:
unit module foo;
sub foo-greet($who) is export(:greet) { ... }
sub foo-greet2($who) is export(:greet2) { ... }
sub foo-greet3($who) is export(:greet3) { ... }