TorontoJim has asked for the wisdom of the Perl Monks concerning the following question:

Thanks to this board, I very recently learned a great deal about Parent/Child modules. Specifically, inheritance and making them work together. I get it, I understand it now. A dangerous statement for sure.

What I would like to do is, however, apparently at odds with this.

I'm writing a module called Date::Math that does arithmetic on dates. I could have one module that does everything, but an end user might only be interested in one kind of math, not all the kinds.

What I would like to do is have Date::Math as the module someone calls, but then the module can load "extender" modules (for want of a better term). For example:

Date::Math::Add; Date::Math::Subtract; Date::Math::DaylightSavingsTime; Date::Math::WeekdayCalculations; Date::Math::JulianCalendar; Date::Math::GregorianCalendar; #etc etc.

However, I don't want someone using the Date::Math to have to expressly call all the modules they want to use in their script.

What I would like to do is have all the children available to Date::Math if the parents method determines one of the children are required.

This would prevent the end user from having to know which modules they needed to "use", and making multiple constructor calls (inherited from Date::Math) to each of the children if they want to "use" more than one. I'm trying to make it one constructor accesses everything.

my $math = Date::Math->new(); print $math->addition("2016/05/16", 5, "days"); if($math->daylightsavingstime_in_effect) { print "DST is in effect."; } print "That falls on a ", $math->weekday_name($math->addition("2016/05 +/16", 5, "days"));

Rather than have the user do something more cumbersome like this below, which I would prefer to avoid:

my $mathadd = Date::Math::Add->new(); my $mathdst = Date::Math::DaylightSavingsTime->new(); my $mathweekday = Date::Math::WeekdayCalculations->new(); print $mathadd->addition("2016/05/16", 5, "days"); if($mathdst->daylightsavingstime_in_effect) { print "DST is in effect."; } print "That falls on a ", $mathweekday->weekday_name($mathadd->additio +n("2016/05/16", 5, "days"));

So what I'm asking, is my concept of using modules this way OK with how we create and use Perl modules? Is there a better way to do what I want to do? Should I just suck it up and accept multiple constructor calls?

I'm just looking for some direction/commentary, should I be rethinking this or am I on the right track?

Replies are listed 'Best First'.
Re: Is there a better way to do this?
by BrowserUk (Patriarch) on Mar 18, 2016 at 22:30 UTC

    Perhaps the simplest method would be to have your main module define stubs for those methods that you want to parcel off into other submodules.

    Something like (Untested. I may have the details wrong, its not something you use every day):

    sub addition { ## stub no strict 'refs'; require 'Date::Math::Add'; *addition = \&Data::Math::Add::addition; goto &addition; }

    The first time the stub method is called, it dynamically requires the sub module and then fixes up the linkage by replacing its own reference with that of the method from the newly loaded module; before finally transferring control (via the special goto) to the loaded method.

    Subsequent calls would go directly to the loaded method.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Is there a better way to do this?
by dsheroh (Monsignor) on Mar 18, 2016 at 23:34 UTC
    If I'm reading your post correctly, you don't seem to be describing parent/child classes, but rather a single class which has the code for each of its methods placed in a separate source file - Date::Math::DaylightSavingsTime isn't overriding Date::Math::daylightsavingstime_in_effect to change its behavior, it's providing the implementation of Date::Math::daylightsavingstime_in_effect.

    And I really don't see any benefit to doing that in 99% of cases. (The other 1% is when you have methods which are sufficiently expensive to compile that you don't want their source code to be processed if the method isn't going to actually be used.)

    It seems like it would be a lot more straightforward to just put all the code into a single module (and a single class).

    That said, there are definitely ways to do the sort of thing you're asking about. The core Perl language provides the AUTOLOAD keyword to dynamically pull in (or generate) the source of not-yet-defined methods on demand. There are also several fine CPAN modules for handling plugins; I personally use all for simple plugin handling and Module::Pluggable for more complex cases.

Re: Is there a better way to do this?
by 1nickt (Canon) on Mar 19, 2016 at 05:21 UTC

    Hi TorontoJim,

    I would compose in my methods with roles. I use Moo and Moo::Role:

    In MyClass.pm -

    package MyClass; use Moo; with 'MyClass::Child'; sub foo { return 'foo'; } 1;
    In MyClass/Child.pm -
    package MyClass::Child; use Moo::Role; sub foo { die 'horribly'; } sub bar { return 'bar'; } 1;
    In your script:
    #!/usr/bin/perl use strict; use warnings; use feature 'say'; use MyClass; my $obj = MyClass->new; say $obj->foo; say $obj->bar; __END__
    Output:
    $ perl 1158279.pl foo bar

    You could also use Role::Tiny directly, but with Moo you'll of course get access to the other sugar like not having to write a constructor, type-checking, etc.

    If you have a whole bunch of roles and for some reason they are expensive to compile you can load them conditionally with with and eval, say based on arguments passed to the constructor call:

    package MyClass; use Moo; sub BUILD { my ( $self, $args ) = @_; my %modules = ( 1 => 'MyClass::MyChild', 0 => 'Acme::Frobnicate', ); my $module = $modules{ $args->{'be_serious'} }; eval "with '$module'; 1;" or die $@; return $self; } sub foo { return 'foo'; } 1;

    As far as whether you should be rethinking what you're doing, there's certainly no harm in reinventing a wheel as an academic exercise ... but I would look at the DateTime project (maybe especially DateTime.pm#How_DateTime_Math_Works), Date::Calc, even Time::Piece before I put too many CPU cycles into tackling date math. I'd also recommend against releasing any code under the Date::Math namespace, until you're very sure that it's at least as reliable as the aforementioned modules.

    Hope this helps!


    The way forward always starts with a minimal test.
      Instead of evaling a with, you can also apply_roles_to_package or apply_roles_to_object .
      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

        Thanks choroba, I didn't know about those Role::Tiny methods.

        I am not sure about whether I prefer them to eval "with '$module'; 1;", though.

        I had to load Role::Tiny explicitly not importing anything, since its before(), after(), around() and with() methods clash with Moo's :

        Subroutine MyClass::before redefined at [...]/5.22.0/Role/Tiny.pm line + 58. Subroutine MyClass::after redefined at [...]/5.22.0/Role/Tiny.pm line +58. Subroutine MyClass::around redefined at [...]/5.22.0/Role/Tiny.pm line + 58. Subroutine MyClass::with redefined at [...]/5.22.0/Role/Tiny.pm line 6 +7.

        And even when I do that I can't use apply_roles_to_object() inside sub BUILD, because the parent's methods haven't been loaded and thus the child's will be.

        package MyClass::Child; use Moo::Role; sub foo { die 'horribly'; } sub bar { return 'bar'; } 1;
        package MyClass; use Moo; use Role::Tiny(); sub BUILD { my $self = shift; Role::Tiny->apply_roles_to_object( $self, 'MyClass::Child' ); return $self; }; sub foo { return 'foo'; } 1;
        Output:
        $ perl -MMyClass -E '$o = MyClass->new; say $o->foo, $o->bar;' horribly at MyClass/Child.pm line 5.

        So the only way I could make it work as part of the Moo constructor was with apply_roles_to_package() :

        package MyClass; use Moo; use Role::Tiny(); sub BUILD { my $self = shift; Role::Tiny->apply_roles_to_package( __PACKAGE__, 'MyClass::Child' +); return $self; }; sub foo { return 'foo'; } 1;
        Output:
        $ perl -MMyClass -E '$o = MyClass->new; say $o->foo, $o->bar;' foobar

        Thanks again for the pointer.


        The way forward always starts with a minimal test.
      Thank you for your responses. Yes, this is an academic exercise on my part, learning how to do this. I've written custom modules for years for proprietary implementations, but I've never written a parent/child scenario before. This is my attempt to learn it as date math is easy, it's the getting things to work how I want ... or changing how I want them to work ... that is the learning curve.