A feature of roles is role groups, which allow you to declare multiple roles with the same name accepting different parameters, similarly to multi routines:
role Foo[Int:D] {
method is-int(::?CLASS:_: --> True) { }
method is-str(::?CLASS:_: --> False) { }
}
role Foo[Str:D] {
method is-int(::?CLASS:_: --> False) { }
method is-str(::?CLASS:_: --> True) { }
}
Often, for one type, you have one module. The problem is you can only have one unit
scoped declaration in a module, so you can't use it with role groups. How can you write a module for a role group?
You can have modules without unit
scoped declarations in them and export symbols from it, but how you export a role group is a bit of a problem. You can't use the is export
trait for this, as that will export the wrong types. When you refer to a role after it has been declared, you're referring to the role group, not the individual roles within it, but using is export
on individual roles will export those individual roles, not the roles' group. Individual roles have a very different HOW from role groups and will not behave like you would normally expect roles to!
Luckily, there's a way to do this using the EXPORT
package. Declaring the Foo
role group in this package will give it a name of EXPORT::DEFAULT::Foo
, which you probably don't want, so you'll want to declare it in the MY
scope of the unit and declare a constant for it in EXPORT::DEFAULT
instead:
use v6.d;
my role Foo[Int:D] {
method is-int(::?CLASS:_: --> True) { }
method is-str(::?CLASS:_: --> False) { }
}
my role Foo[Str:D] {
method is-int(::?CLASS:_: --> False) { }
method is-str(::?CLASS:_: --> True) { }
}
my package EXPORT::DEFAULT {
constant Foo = ::Foo;
}
Now Foo
can be imported and used OK:
use Foo;
say ::<Foo>:exists; # OUTPUT: True
say try Foo[1].is-int; # OUTPUT: True
say try Foo['ok'].is-str; # OUTPUT: True
Note: you can't use ::
in constant names, so to export a role group in a namespace you'll need to wrap it in another package:
my role Foo::Bar[Int:D] { }
my role Foo::Bar[Str:D] { }
my package EXPORT::DEFAULT {
package Foo {
constant Bar = Foo::Bar;
}
}
Simple, just use the default our
scope without a surrounding unit
anything.
unit
was only added so that you wouldn't have to surround a whole file with {
and }
when there was only one module
, package
, class
, role
, or sub
in the file.
You don't always have to use it.
In fact you never have to use it.
If you want, add a forward declaration without a parameterization.
A trait added to it will generally apply to all of the roles with the same name.
lib/Foo/Bar.rakumod
:
use v6.d;
role Foo::Bar {…} # is export would be added here
role Foo::Bar[Int:D] {
method is-int(::?CLASS:_: --> True) { }
method is-str(::?CLASS:_: --> False) { }
}
role Foo::Bar[Str:D] {
method is-int(::?CLASS:_: --> False) { }
method is-str(::?CLASS:_: --> True) { }
}
Then when you use it, it automatically gets loaded in such a way that it is accessible by fully qualified name.
{
use lib <lib>; # only needed because it is not installed
use Foo::Bar;
say Foo::Bar[ 1].is-int; # True
say Foo::Bar[''].is-str; # True
say Foo::Bar.^name; # Foo::Bar
}
say Foo::Bar.^name; # error: Could not find symbol 'Bar' in 'Foo'
In this case you can put it inside of a module statement so that you wouldn't need to write Foo::
so often.
lib/Foo/Bar.rakumod
:
use v6.d;
unit module Foo;
role Bar {…}
role Bar[Int:D] {
method is-int(::?CLASS:_: --> True) { }
method is-str(::?CLASS:_: --> False) { }
}
role Bar[Str:D] {
method is-int(::?CLASS:_: --> False) { }
method is-str(::?CLASS:_: --> True) { }
}
The role is still accessible as Foo::Bar
.
I wouldn't be surprised if this resulted in the exact same code as the previous example.
The only reason to add is export
is if you wanted it to be exported as Bar
instead of Foo::Bar
. This applies to both of the above examples.
My guess is that you thought that you always needed to use is export
. In many cases you really don't.
unit module Foo::Bar; # default `our` scoped
our sub baz ( --> 'hello world'){}
use Foo::Bar;
say Foo::Bar::baz(); # hello world
# this works because it was declared as `our`
If you wanted to be able to just use baz()
while having it inside of the scope of the module, then and only then do you need to export it.
unit module Foo::Bar;
our sub baz ( --> 'hello world') is export {}
use Foo::Bar;
say Foo::Bar::baz(); # hello world
# available because of `is export`
say baz(); # hello world
Note that I still declared it as our
so that if someone didn't want you to export it, it is still accessible to them.
use Foo::Bar ();
# say baz(); # baz used at line 1. Did you mean 'bag'?
say Foo::Bar::baz(); # hello world
The whole purpose of is export
is to remove the need to use fully qualified names for functions. That it also works for things like roles is a side benefit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With