Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you write a module for a role group?

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?

like image 630
Kaiepi Avatar asked Mar 27 '20 01:03

Kaiepi


2 Answers

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;
    }
}
like image 162
Kaiepi Avatar answered Oct 23 '22 03:10

Kaiepi


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.

like image 4
Brad Gilbert Avatar answered Oct 23 '22 03:10

Brad Gilbert