Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

One module to use them all (proxy moudle that exports subs of other modules)

by nataraj (Sexton)
on Sep 02, 2022 at 20:01 UTC ( [id://11146642]=perlquestion: print w/replies, xml ) Need Help??

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

Hi monks!

Let's imagine I have several modules, like MyMod1, MyMod2, MyMod3. They exports some of their functions, and their export list is not fixed and changes from time to time

I want to create a "proxy" module, MyModAll, so when you say

use MyModAll;

it would be equivalent of saying

use MyMod1; use MyMod2; use MyMod3;

I would not believe, that it is not possible to do such a thing in perl. You can do anything in perl

But I did not managed to figure out, how to do it. Can you help me with that please!

P.S. My original issue is TL;TR, so I kept story short, keeping exactly to the point. So I need seamless using of one module via using another, not some other solution

Thank you in advance!

Update: Partially solved here. This solution works for modules that uses Export for exporting. For other modules I added desired subs to export list explicitly. This did most of the trick.

Replies are listed 'Best First'.
Re: One module to use them all (proxy moudle that exports subs of other modules)
by choroba (Cardinal) on Sep 02, 2022 at 21:00 UTC
    tldr.pl
    #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; use ModAll; say for m1(), m2(), m3();

    Mod1.pm

    package Mod1; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m1 ); sub m1 { 'm1' } __PACKAGE__

    Mod2.pm

    package Mod2; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m2 ); sub m2 { 'm2' } __PACKAGE__

    Mod3.pm

    package Mod3; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m3 ); sub m3 { 'm3' } __PACKAGE__

    ModAll.pm

    package ModAll; use warnings; use strict; use Mod1; use Mod2; use Mod3; use Exporter 'import'; our @EXPORT = (@Mod1::EXPORT, @Mod2::EXPORT, @Mod3::EXPORT); __PACKAGE__

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      So easy! But this worked for me!

      Thank you for your answer

      Oups... Sorry... not that easy... Some of MyMod* modules (that are not really mine) does not use Exporter. And this breaks everything :-(

        Based on choroba's ++reply, here's a (partial?) solution (?) that may be closer to what you want.

        tldr.pl:

        # from choroba pm#11146644 (modified) use warnings; use strict; use ModAll; print "'$_' \n" for Foo::bar(qw(some stuff)), m1(), m2('whatever'), m3 +;
        ModAll.pm:
        # from choroba pm#11146644 (modified) package ModAll; use warnings; use strict; # use Data::Dump qw(dd); # for debug use constant USE_ALL => qw(Mod1 Mod2 Mod3 Foo); eval "use $_" for USE_ALL; use Exporter 'import'; our @EXPORT = map eval, map "\@${_}::EXPORT", USE_ALL ; __PACKAGE__
        Mod1.pm:
        package Mod1; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m1 ); sub m1 { "m1(@_)" } __PACKAGE__
        Mod2.pm:
        package Mod2; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m2 ); sub m2 { "m2(@_)" } __PACKAGE__
        Mod3.pm:
        package Mod3; use warnings; use strict; use Exporter 'import'; our @EXPORT = qw( m3 ); sub m3 { "m3(@_)" } __PACKAGE__
        Foo.pm (forgot this previously):
        # Foo.pm package Foo; use strict; use warnings; sub bar { sprintf "hiya from -%s-(@_) invoked from '%s'", (caller 0)[3, 0]; } 1;
        Invocation:
        Win8 Strawberry 5.8.9.5 (32) Fri 09/02/2022 19:51:48 C:\@Work\Perl\monks\nataraj >perl tldr.pl 'hiya from -Foo::bar-(some stuff) invoked from 'main'' 'm1()' 'm2(whatever)' 'm3()'
        (Caution: There may be some important phasing considerations in my version of ModAll.pm that I'm overlooking!)

        Update: Forgot to list the Foo.pm module. It's listed elsewhere, but I've added it here for completeness.


        Give a man a fish:  <%-{-{-{-<

        Some of MyMod* modules ... does not use Exporter.

        So what do they use?

        Is it possible that some of the modules do not export at all, i.e., the fully-qualified name of a subroutine or package-global variable defined in the module must be used to invoke the subroutine/variable? E.g.:
        File Foo.pm:

        # Foo.pm package Foo; use strict; use warnings; sub bar { printf "hiya from -%s-(@_) invoked from '%s' \n", (caller 0)[3, 0] +; } 1;
        Invocation:
        Win8 Strawberry 5.8.9.5 (32) Fri 09/02/2022 18:25:19 C:\@Work\Perl\monks\nataraj >perl -wMstrict -le "use Foo; Foo::bar(qw(some stuff)); print 'ok'" hiya from -Foo::bar-(some stuff) invoked from 'main' ok
        This situation can be addressed, but we need these details.


        Give a man a fish:  <%-{-{-{-<

        And this breaks everything :-(

        I don't see how the absence of Exporter in any of those modules would cause breakage.
        Can you provide more details ?

        Cheers,
        Rob
Re: One module to use them all (proxy moudle that exports subs of other modules)
by TheDamian (Vicar) on Sep 02, 2022 at 22:22 UTC
Re: One module to use them all (proxy moudle that exports subs of other modules)
by pryrt (Abbot) on Sep 03, 2022 at 19:15 UTC
    My take, with one of the sub-modules being Exporter-based, the second using its own import(), and the One Module still being able to figure out how to bind them together and tell the world what to do:

    11146642-tldr.pl

    #!perl use 5.012; # //, strict, say use warnings; use FindBin; use lib "$FindBin::Bin/lib"; use Mod11146642::All; # Mod11146642::One uses Exporter # Mod11146642::Two uses manual import() # but both work through Mod11146642::All oneFunction(); twoFunction();

    Mod11146642/All.pm

    package Mod11146642::All 1.00; use 5.012; # //, strict, say use warnings; use Exporter 5.47 qw(import); my %colons; my @inherited; BEGIN { $colons{$_} = $_ for keys %::Mod11146642::All::; } use Mod11146642::One; use Mod11146642::Two; BEGIN { for (sort keys %::Mod11146642::All::) { next if /^__ANON__$/; # ignore anonymous functions next if exists $colons{$_}; # ones that were in the namesp +ace before weren't inherited push @inherited, $_; # if we're here, we inherited +this } # local $" = ","; warn "inherited (@inherited)\n"; # un-comment + this line if you want to debug the ineritance check } our @EXPORT = @inherited; 1;

    Mod11146642/One.pm

    package Mod11146642::One 1.00; use 5.012; # //, strict, say use warnings; use Exporter 5.47 qw(import); our @EXPORT = qw(oneFunction); sub oneFunction { local $" = ","; printf STDERR "Called %s(@_)\n", (caller(0))[3]; } 1;

    Mod11146642/Two.pm

    package Mod11146642::Two 2.00; use 5.012; # //, strict, say use warnings; sub twoFunction { local $" = ","; printf STDERR "Called %s(@_)\n", (caller(0))[3]; } sub import { my ($pkg) = @_; my $callpkg = caller(0); no strict 'refs'; my $exportfunction = $callpkg . '::twoFunction'; *{$exportfunction} = \&twoFunction; } 1;
      The OP has existing modules which do not have an import function because they do not have use EXPORTER;. Would it not be easier to add that line than a custom import function? Adding *twoFunction =\&Mod11146642::All::twoFunction; to the new module Mod11146642::All should do the same thing for this application and not affect anything else.

      Long UPDATE to Clarify my point

      Nataraj has posted the following update to One module to use them all (proxy moudle that exports subs of other modules).
      Update: Partially solved here. This solution (Re: One module to use them all (proxy moudle that exports subs of other modules)) works for modules that uses Export for exporting. For other modules I added desired subs to export list explicitly. This did most of the trick.
      From this I assume that most functions ‘work’ (i.e. The application tldr.pl can access them with unqualified names) . Later, he tells us that all the exceptions are in third party (‘not really mine’) modules which do not have use EXPORTER. It is reasonable to assume that these modules do not have an appropriate ‘import’ function. At least three solutions have been proposed. Any one of them would ‘work’ . None of them are exactly what the OP was hoping for.
      • Manually ‘import’ the function name(s) into the proxy module.
      • Edit the module to add use EXPORT
      • Edit the module to add a custom import function.

      The second and third option are probably not available for third party modules. The first option has the advantage that all extra code is added to the ‘proxy’ module ‘ModAll’. This method fails to meet the OP's expectations only because the proxy module must be manually updated every time the library of third party functions is reorganized.

      Demo of ‘manual’ method

      Modification of Re: One module to use them all (proxy moudle that exports subs of other modules)

      The following files are unchanged

      • tldr.pl
      • Mod1.pm
      • Mod2.pm

      EXPORTER has been removed from Mod3.pm to simulate a 'third party' module.

      package Mod3; use warnings; use strict; #use Exporter 'import'; #our @EXPORT = qw( m3 ); sub m3 { 'm3' } __PACKAGE__

      Manual ‘import’ added to ModAll.pm

      package ModAll; use warnings; use strict; use Mod1; use Mod2; use Mod3; use Exporter 'import'; # Supply function name ‘m3’ manually (not in any EXPORT list) #our @EXPORT = (@Mod1::EXPORT, @Mod2::EXPORT, @Mod3::EXPORT); our @EXPORT = (@Mod1::EXPORT, @Mod2::EXPORT, ‘m3’); *m3 = \&Mod3::m3 # hand code result of missing import; __PACKAGE__
      Bill
        Would it not be easier to add that line than a custom import function?

        That assumes that you know all the functions exported by all the sub-modules before designing the All.pm module. The OP specifically contradicted this assumption: "their export list is not fixed and changes from time to time".

        So, ::All needs to be designed in such a way that if ::Two v2.00 exports just twoFunction() , but ::Two v2.01 exports frobnicate() as well, ::All can still work without having to be updated.

        > The OP has existing modules which do not have an import function because they do not have use EXPORTER;.

        Sorry, this doesn't make sense.

        You can have a sub import function without using the customized one provided from the module Exporter °

        And without import no way to export.

        Hence if the OP wanted to call those module-subs fully qualified anyway (because they are not exported) there wouldn't be any problem. He could just require the modules, and call the subs in their native package.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        °) have a look at the provided Two.pm "using its own import()"

Re: One module to use them all (proxy module that exports subs of other modules)
by LanX (Saint) on Sep 03, 2022 at 01:18 UTC
Re: One module to use them all (proxy moudle that exports subs of other modules)
by NERDVANA (Deacon) on Sep 04, 2022 at 21:31 UTC
    I made a fairly complete solution to this problem with Exporter::Extensible.
    package MyMod1; use Exporter::Extensible -exporter_setup => 1; sub foo :Export( :default ) { 42 }
    package MyMod2; use Exporter::Extensible -exporter_setup => 1; sub bar :Export( :default ) { 43 }
    package MyMod3; use parent qw( MyMod1 MyMod2 );
    perl -I. -MMyMod3 -E 'say foo'
    However, I also recommend that you don't export things by default. It isn't much more to type use MyMod3 ':all'; and then it gives you more flexibility in the future to choose fewer imports.
    package MyMod1; use Exporter::Extensible -exporter_setup => 1; sub foo :Export { 42 }
    package MyMod2; use Exporter::Extensible -exporter_setup => 1; sub bar :Export { 43 }
    package MyMod3; use parent qw( MyMod1 MyMod2 );
    perl -I. -MMyMod3=:all -E 'say foo'
      However, I also recommend that you don't export things by default.

      This. Polluting other people's namespaces is just rude. That isn't the Perl way. Permit the import - don't enforce it.


      🦛

Re: One module to use them all (proxy moudle that exports subs of other modules)
by nataraj (Sexton) on Sep 03, 2022 at 07:43 UTC

    Thank all of you for answering. Question is partly solved, as was suggested here and for the modules that does not use Exporter, I just manually listed subroutines I need in my own @EXPORT list, and this did the trick.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (5)
As of 2024-04-19 06:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found