5

I am seeking to re-use the same role/class names in a child module as in its parent. You know, like a specialization.

The aim is to be able to re-use the same script code for both the parent and child Series variants by simply changing use Dan to use Dan::Pandas at the top.

I am trying to stick to role rather than class compostion where I can so that the behaviours can be applied to other objects with eg. class GasBill does Series;

Here is the MRE:

me@ubuntu:~/spike# tree
.
├── lib
│   ├── Dan
│   │   └── Pandas.rakumod
│   └── Dan.rakumod
└── spike.raku

Dan.rakumod:

  1 unit module Dan;
  2 
  3 role Series is export {
  4     method no { "no" }
  5 }

Pandas.rakumod:

  1 unit module Dan::Pandas;
  2 
  3 use Dan;
  4 
  5 role Series does Dan::Series is export {
  6     method yo { "yo" }
  7 }

spike.raku:

  1 use lib './lib';
  2 use Dan::Pandas;
  3 
  4 my $s = Series.new;                  #version 1                      
  5 #my $s = Dan::Pandas::Series.new;    #version 2 "fqm"
  6 
  7 say $s.no, $s.yo, $s.^name;

My version 1 says:

No appropriate parametric role variant available for 'Dan::Series':
    Ambiguous call to '(Dan::Series)'; these signatures all match:
      (::$?CLASS ::::?CLASS Mu $)
      (::$?CLASS ::::?CLASS Mu $)
  in block <unit> at spike.raku line 4

My version 2 says:

Could not find symbol '&Series' in 'Dan::Pandas'
  in block <unit> at spike.raku line 5

I am trusting that raku does have a way to do this, but darned if I can work it out!

librasteve
  • 6,832
  • 8
  • 30

3 Answers3

5

If I'm understanding correctly, you don't need/want to use the non-specialized role in the final module (that is, you aren't using the Series defined in Dan.rakumod in spike.raku – you're only using the specialized Series defined in Pandas.rakumod). Is that correct?

If so, the solution is simple: just don't export the Series from Dan.rakumod – it's still our scoped (the default for roles) so you can still use it in Pandas.rakumod exactly the way you currently do (Dan::Series). But, since it's not exported, you it won't create a name clash with the non-prefixed Series.

codesections
  • 8,900
  • 16
  • 50
  • 1
    that's the right reading of the question - however, I do want other scripts to be able to ```use Dan;``` (ie the non-Pandas version) successfully – librasteve Mar 04 '22 at 12:56
  • 2
    @p6steve other scripts can still `use Dan` just fine in exactly the same way that `Pandas.rakumod` does – they'll just need to use the full `Dan::Series` name (rather than just `Series`) to refer to the `Dan::Series` (again, just like `Pandas.rakumod` does). If that's too verbose, they can always alias it in the file with something like `constant Series = Dan::Series` after the `use` statement. – codesections Mar 04 '22 at 15:10
  • codesections - thanks for the reminder that FQNs do not rely on the export process... this is a keystone for my combined solution (see below) ... since I only have one vote for Answer, please forgive that I will award to @raiphs answer this time ... since that closed the loop. – librasteve Mar 05 '22 at 10:05
5

TL;DR You need a solution that avoids having two roles with the same full name.

Golf

role R {}
role R {}
R.new

displays:

No appropriate parametric role variant available for 'R':
    Ambiguous call to '(R)'; these signatures all match:
      (::$?CLASS ::::?CLASS Mu $)
      (::$?CLASS ::::?CLASS Mu $)

(See Gut-referencing warning when composing stubbed role for a bit more discussion of the error message.)

A solution

  1. Just Dan.

    • Dan.rakumod:

      unit module Dan;
      
      role Series is export { method no { "no" } }
      
    • spike.raku:

      use lib '.';
      
      use Dan;
      
      my $s = Series.new;
      
      .say for $s.?no, $s.?yo, $s.^name;
      
    • Running spike displays:

      no
      Nil
      Dan::Series
      
  2. Introduce Dan::Pandas.

    • Alter Dan so variant modules can use it without them importing Dan's unqualified Series symbol into their lexical scopes.

      (Because otherwise Dan's unqualified Series symbol ends up being in their lexical scopes as well as their own unqualified Series symbol, and you get the "Ambiguous call" error.)

    • Dan:

      unit module Dan;
      
      role Series is export { method no { "no" } }
      
      class Dan::Variant-Export-Dummy-Type is export(:Variant-Exports) {}
      

      The change is the last line, a dummy declaration with a new export tag. (Introducing the new tag some cleaner way is left as an exercise for readers.)

    • Dan::Pandas:

      unit module Dan::Pandas;
      
      use Dan :Variant-Exports;
      
      role Series does Dan::Series is export { method yo { "yo" } }
      

      The change is using the new :Variant-Exports tag with the use Dan ... statement. This stops Dan's Series role being imported under that unqualified name into Dan::Pandas.

  3. User programs will now work as expected/wanted.

    • Just change the use statement in spike.raku:

      use lib '.';
      
      use Dan::Pandas;
      
      my $s = Series.new;
      
      .say for $s.?no, $s.?yo, $s.^name;
      
    • Running with just this change for user programs:

      no
      yo
      Dan::Pandas::Series
      
    • Note how there's no need for programs using using these modules to know about the :Variant-Exports tag. That's just an internal detail of the Dan module and the Dan::Pandas etc modules that lets them avoid the unintended role variant duplication problem shown at the start of this answer.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • 1
    raiph - many thanks for this ... your answer was slightly offbeam (no doubt due to my poor naming of methods in the op) in that ```method no``` should always work (either directly or inherited by the child) and that ```method yo``` should only work when the child is there since it is the specilization. I have adopyed your approach and applied export controls ... see 'My Solution' below for the synthesis of yoru answer (also with a nudge from @codesections) – librasteve Mar 05 '22 at 10:09
  • @p6steve "your answer was slightly offbeam ... in that `method no` should always work" Ah. I've updated my answer accordingly so that the `.no` call always works (by adding `does Dan::Series` in the `Dan::Pandas` module). – raiph Mar 07 '22 at 17:54
1

My Solution

Taking the advice of @codesections [that FQMs can be used without is export] and @raiph [that you can qualify the export with e.g. is export(:ALL)], then - yes - there is a neat way to do this.

viz. https://docs.raku.org/language/modules#Exporting_and_selective_importing

Turns out that this is low touch (just change two lines) and, as I originally hoped, something that raku designers have anticipated.

Here is the code from my OP adjusted...

Dan.rakumod:

  1 unit module Dan;
  2 
  3 role Series is export(:ALL) {    #<== added the selective export
  4     method no { "no" }
  5 }  

Pandas.rakumod (no change):

  1 unit module Dan::Pandas;
  2 
  3 use Dan;
  4 
  5 role Series does Dan::Series is export {
  6     method yo { "yo" }
  7 }  

spike.raku:

  1 use lib './lib';
  2 #use Dan::Pandas;     #\ either of these work
  3 use Dan :ALL;         #/ <== use :ALL to get 'Series'
  4 
  5 my $s = Series.new;
  6 
  7 say $s.no, $s.yo, $s.^name;  #No such method 'yo' if Pandas not used
librasteve
  • 6,832
  • 8
  • 30