Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Breaking up a big module

by talexb (Canon)
on Apr 25, 2019 at 08:38 UTC ( #1232977=perlquestion: print w/replies, xml ) Need Help??

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

I have a module like this:

package Foo; sub aa { my ( $self, $args ) = @_; my $val = $self->_aa ( $args->{'bar'} ); } sub _aa { my $self, $quux ) = @_; } sub bb { my ( $self, $args ) = @_; my $val = $self->_bb ( $args->{'foo'} ); } sub _bb { my $self, $fazoo ) = @_; }
and I'd like to break it up into smaller modules. The normal sub names are public methods, and the sub names with the leading underbar are private methods, so the private methods could go into another module. I managed to do
package Foo::Bar; sub _aa { my $self, $quux ) = @_; } ..
and call that as
package Foo; sub aa { my ( $self, $args ) = @_; my $val = Foo::Bar::_aa ( $self, $args->{'bar'} ); } ..
but that's a bigger change than I'd like, and isn't as clean as the original.

I'd like to be able to have Foo::Bar (with the _aa method, and others) just be more of the Foo class (so I could still call $self->_aa in Foo, but the sub _aa would live in Foo::Bar. There's a way, but my digging so far hasn't uncovered it.

Alex / talexb / Toronto

Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Replies are listed 'Best First'.
Re: Breaking up a big module
by tobyink (Abbot) on Apr 25, 2019 at 09:44 UTC

    Okay, so by convention, you'd have a module with filename "Foo/Bar.pm" and in that you'd define a package called Foo::Bar. That's just a convention though! "Foo/Bar.pm" can define a package called "Foo".

    Foo.pm

    package Foo; require Foo::Bar; # load additional methods sub aa { my ( $self, $args ) = @_; my $val = $self->_aa ( $args->{'bar'} ); } sub bb { my ( $self, $args ) = @_; my $val = $self->_bb ( $args->{'foo'} ); } 1;

    Foo/Bar.pm

    package Foo; # not Foo::Bar!!! sub _aa { my ( $self, $quux ) = @_; ...; } sub _bb { my ( $self, $fazoo ) = @_; ...; } 1;

    Also, you don't need to do your require Foo::Bar thing right at the top of Foo.pm like I did in my examples. You could do something like:

    sub aa { my ( $self, $args ) = @_; require Foo::Bar; my $val = $self->_aa ( $args->{'bar'} ); }

    … which will load the additional methods right before they're needed instead of right at the start. If those private methods are uncommonly used (for example, they're used in debugging and diagnostics only, and normal executions of the program don't need them) then this might be a good idea, because you can skip loading, parsing, and compiling those methods most of the time. If the methods are going to be used all/most of the time, you'll get better performance from a single require Foo::Bar at the top of Foo.pm.

    I go to even more extremes to split up my CPAN module Types::Standard. Some of the methods defined in that are instead blessed objects overloading coderefification which when they're first called, load their real code from another module, and replace themselves. It's pretty rare that you'd want to go to those extremes, but it makes sense for Types::Standard.

      I think thats pretty close to what I described here

      But I'd require inside a BEGIN block or just use use to make sure that non method calls without parens work and other side effects where timing matters.

      The other point is namespace pollution, one might not want such partial "split-modules" to show up in the normal @INC.

      My approach was to add underscores to package names and having a dedicated extra load path.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Breaking up a big module
by tobyink (Abbot) on Apr 25, 2019 at 09:55 UTC

    I've already answered this question, but now I'm typing a second solution.

    Foo.pm

    package Foo; use Role::Tiny::With; with "Foo::Bar"; sub aa { my ( $self, $args ) = @_; my $val = $self->_aa ( $args->{'bar'} ); } sub bb { my ( $self, $args ) = @_; my $val = $self->_bb ( $args->{'foo'} ); } 1;

    Foo/Bar.pm

    package Foo::Bar; use Role::Tiny; sub _aa { my $self, $quux ) = @_; } sub _bb { my $self, $fazoo ) = @_; } 1;

    This has the advantage that the methods defined in Foo::Bar are no longer coupled to Foo. If you define a new class called Foo2, and want to have _aa and _bb defined in Foo2 as well, you can just do:

    package Foo2; use Role::Tiny::With; with "Foo::Bar"; ...; 1;
Re: Breaking up a big module
by Eily (Monsignor) on Apr 25, 2019 at 09:19 UTC

    I feel like I might be missing the point, but isn't parent what you are looking for? If you have use parent 'Foo::Bar'; at the top of you Foo package, any call of the form $obj->method with $obj blessed into Foo will try to call Foo::method, and failing that, try again for every module in the @ISA list (in this case Foo::Bar).

      I was writing this code while you were posting...

      use strict; use warnings; package Foo; use parent 'Foo_Bar'; sub new { my $class = shift; return bless {}, $class; } sub aa { my ( $self, $args ) = @_; my $val = $self->_aa ( $args->{'bar'} ); print "aa\n"; } package main; my $foo = Foo->new(); $foo->aa( { arg1 => 'some' } );

      and in Foo_Bar.pm (underscore instead of :: only for my convenience):

      use strict; use warnings; package Foo_Bar; sub _aa { my( $self, $quux ) = @_; print "\t_aa\n"; } 1;
      I'd say yes ....

      ... provided all his calls are already ->method calls and all objects/$self are always blessed into Foo and never into Foo::Bar and his not trying to use SUPER ...

      In short as long as this approach doesn't lead to multiple inheritance of third party classes.

      So probably importing is safer.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        So probably importing is safer.
        It certainly is the more transparent solution, at least. But the methods that must be moved are used in a limited scope (they are private), so checking for correct calls might not be too cumbersome.

        As always, TIMTOWTDI :)

        This is probably where all the C++ books explain the difference between "is a" and "uses"...

Re: Breaking up a big module (low tech require same namespace)
by LanX (Archbishop) on Apr 25, 2019 at 09:08 UTC
    We are handling similar problems at the moment.

    First of all you are dealing with a OO class, so there are even more options available.

    In our case the main author created a very large module with a lot of cross dependencies, and other can't discuss it anymore with him.

    To make it clearer, if Foo::Bar::_aa() is calling Foo::Blabla::_bb(), i.e. both are refactored into different modules you need to adjust the call to &_bb() too.

    IF all your calls so far where ->method calls, you could try to import them into the main class Foo, before the first code is run.

    This is very close to OO concepts named roles, mixins, traits , ...

    But our case was more complicated, because we have also function calls.

    So I convinced the maintainer to adapt a "evolutionary" low-level approach and to just move the functions in logical groups into several _Foo::Whatever "modules" but all belonging to "package Foo;" and to requiring them at compile time from the top-module (aka use without import).

    Like this can freely move stuff into sub-files without fearing any conflicts (NB: there are no filescoped lexicals in this file)

    This helps us to get an overview over the stable product and discuss necessary refactorizations on parts of the application. (Divide and Conquer)

    The goal is of course to have proper sub-modules/classes in the end.

    edit

    Of course, like already said, if your case is less complicated and all your calls are ->method calls, try as I told you and import _aa() from Foo::Bar into Foo::

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Breaking up a big module
by karlgoethebier (Monsignor) on Apr 25, 2019 at 10:10 UTC

    Very interesting. Good advice was already given. Some stuff for further inspiration.

    From there:

    "...The main idea of role-oriented programming is that humans think in terms of roles. This claim is often backed up by examples of social relations. For example, a student attending a class and the same student at a party are the same person, yet that person plays two different roles. In particular, the interactions of this person with the outside world depend on his current role. The roles typically share features, e.g., the intrinsic properties of being a person. This sharing of properties is often handled by the delegation mechanism..."
    "...Many researchers have argued the advantages of roles in modeling and implementation. Roles allow objects to evolve over time, they enable independent and concurrently existing views (interfaces) of the object, explicating the different contexts of the object, and separating concerns. Generally roles are a natural element of human daily concept-forming. Roles in programming languages enable objects to have changing interfaces, as we see in real life - things change over time, are used differently in different contexts, etc."

    Yet another promised land. You may also take a look at the many links provided ibidem and as well at Roles represent objects that interact to achieve some purpose... by Trygve Reenskaug.

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: Breaking up a big module
by Fletch (Chancellor) on Apr 25, 2019 at 09:51 UTC

    Were this Ruby or Moose I'd almost say you want a mixin / role to hold the private methods . . . maybe? Moose::Manual::Roles

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Breaking up a big module
by pwagyi (Scribe) on Apr 25, 2019 at 21:51 UTC

    If private methods are really tied to module Foo then I don't see what is really gained by separating into separate module file? Are private methods re-used elsewhere?

      Just imagine that the Foo module is really, really large, and the goal is to break it up into a bunch of smaller modules. :)

      Alex / talexb / Toronto

      Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

        But breaking it up based on public/private is not very natural (as indicated by pwagyi). Have you considered breaking it up based on functionality e.g. by extracting helper classes/objects?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1232977]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (3)
As of 2019-08-26 06:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?