Search code examples
perlmoops

Import functions/subroutine into subclass


I'm using Moops and I'd like something like this to work:

use Moops;

class A {
  fun f {
    print "yay,f!\n";
  }
}

class B extends A {
  fun g {
    f();
  }
}
B->g(); # should print 'yay, f!'

Instead this yields:

Undefined subroutine &B::f called at static-functions-lexical-scope.pl line 11.

I can "fix" this by inheriting from Exporter in A and a use statement in B like so:

class A extends Exporter {
  our @EXPORT = qw(f);
  fun f {
    print "yay,f!\n";
  }
}

class B extends A {
  use A;
  fun g {
    f();
  }
}

This seem a bit unwieldy, but it gets worse if A is defined in another file. Then I'd have to add a second use A (require won't do) outside of B like so:

use  A;
class B extends A {
  use A;
  fun g {
    f();
  }
}

Is there a way to make lexical inclusion (of exported) functions work more elegantly?


Solution

  • First, thanks for using Moops! :-)

    Second, it's probably a bad idea to name classes "B" in tests and examples. There's a module called B that comes with Perl, and Moops actually uses it!

    Coming to your actual question, generally speaking with classes, you should be thinking method rather than fun. Method calls respect inheritance; function calls don't.

    use Moops;
    
    class AAA {
      method f {
        say "yay,f!";
      }
    }
    
    class BBB extends AAA {
      method g {
        $self->f;
      }
    }
    
    BBB->g;
    

    Outputs:

    yay,f!
    

    If you want a library of convenience functions to be available in both AAA and BBB then, split those convenience functions into a separate package:

    use Moops;
    
    namespace MyUtils {
      use Exporter::Shiny qw( f );
      fun f {
        say "yay,f!";
      }
    }
    
    class AAA {
      use MyUtils -all;
      method m1 {
        f();
      }
    }
    
    class BBB extends AAA {
      use MyUtils -all;
      method m2 {
        f();
      }
    }
    
    BBB->m1;
    BBB->m2;
    

    Outputs:

    yay,f!
    yay,f!
    

    You can even go a bit further and extend Moops from within. The following example defines a :utils trait that can be added to classes:

    use Moops;
    
    namespace MyUtils {
      use Exporter::Shiny qw( f );
      fun f {
        say "yay,f!";
      }
    }
    
    role Moops::TraitFor::Keyword::utils {
       around generate_package_setup {
         return (
           $self->$next(@_),
           'use MyUtils -all;',
         );
       }
    }
    
    class AAA :utils {
      method m1 {
        f();
      }
    }
    
    class BBB extends AAA :utils {
      method m2 {
        f();
      }
    }
    
    BBB->m1;
    BBB->m2;
    

    Same output as previous example.