2

This is an interesting Perl behaviour. (atleast to me :) )

I have two packages PACKAGE1 and PACKAGE2 which exports function with same name Method1().

As there will be so many packages which will export this same function, use-ing everything in a Perl file will be tedious. So, I have created a general include file INCLUDES.pm which will have these uses.

INCLUDES.pm:

use PACKAGE1;
use PACKAGE2;

1;

PACKAGE1.pm:

package PACKAGE1;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
          Method1
);

sub Method1{
print "PACKAGE1_Method1 \n";
}

1;

PACKAGE2.pm:

package PACKAGE2;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
    Method1
);

sub Method1{
    print "PACKAGE2_Method1 \n";
}

1;

Tests.pl:

##################first package################
package Test1;
use INCLUDES;
my @array = values(%INC);
print  "@array  \n";

Method1();

##################second package################
package Test2;
use INCLUDES;  #do "INCLUDES.pm";
my @array = values(%INC);
print  "@array  \n";

Method1();

The motive is, only the latest package's Method1() should be used in any Perl file.

The output surprises me. I would expect that both Method1() calls in Tests.pl should be success. But only the first Method1() executes, the second Method1() call says "undefined".

OUTPUT:

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pmINCLUDES.pm

PACKAGE2_Method1

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pm INCLUDES.pm

Undefined subroutine &Test2::Method1 called at C:\Temp\PackageSample\Tests.pl line 15.

Do somebody have any answers/views on this?

The actual scenario:

the methods in multiple Perl modules will be having same name. But the methods from the High preference perl module should only be used.

For example, if PACKAGE1 contains Method1(), Method2() & PACKAGE2 contains only Method1(), then Method1() should be used from PACKAGE2 & Method2() should be used from PACKAGE1

Basically I want to achieve a Hierarchy among modules based on Preference. Is there any way for this?

InnovWelt
  • 660
  • 1
  • 8
  • 21
  • 1
    While I can try to dream up some way to make it "work" I have two questions. 1) What should the second output be ("PACKAGE2_Method1" as well?). 2) Could you use some kind of class dispatch so that you don't have this giant ambiguity, it seems like it will be a debugging nightmare for someone down the road. – Joel Berger Feb 02 '13 at 05:42
  • Yes. the second output will be also "PACKAGE2_Method1". The actual scenario is that, the methods in multiple Perl modules will be having same name. But the methods from the High preference perl module should only be used. For example, if `PACKAGE1` contains `Method1(), Method2()` & `PACKAGE2` contains only `Method1()`, then `Method1()` should be taken from `PACKAGE2` & `Method2()` should be taken from `PACKAGE1`. Basically I want to achieve a Hierarchy among modules based on Preference. – InnovWelt Feb 02 '13 at 06:38
  • 1
    I agree with Joel that this sounds like a maintenance nightmare in the making - when the maintenance programmer (quite likely a future version of yourself) sees a call to `Method1`, he won't know where to look for its definition and will, very likely, look in the wrong place and attempt to debug code that isn't even being run. When you say "Hierarchy among modules", that gives me the impression that you're trying to reinvent object-oriented inheritance and polymorphism; if so, use the existing, proven OO techniques instead of reinventing them poorly. – Dave Sherohman Feb 02 '13 at 08:45

2 Answers2

4

In Perl, use Module is equivalent to

BEGIN { require Module; Module->import; }

But require caches the list of modules that have been required. It only loads the module once per Perl process. So only the first use IMPORTS does anything. Since your IMPORTS module doesn't have an import method, nothing happens when you use it again.

I'm not quite sure what you're attempting to accomplish. Perhaps your IMPORTS module should be an actual package, with an import method that exports whatever functions you want. That way, each use IMPORTS would export functions into the package that called it.

cjm
  • 61,471
  • 9
  • 126
  • 175
  • I had spent some time working on how to do it "right" (actually I still think the design is fundamentally flawed, I mean "working"), but I was scratching my head on why one worked and the other didn't. You are certainly right about the caching prevents the second. What I still can't figure out is how the first worked? – Joel Berger Feb 02 '13 at 06:06
  • @JoelBerger, the first one works because there's no `package` statement in `IMPORTS.pm`. So the active package when `IMPORTS.pm` loads is still `Test1`, and the `use PACKAGE` statements import functions into that package. But because it's already loaded, no code is executed by the second `use IMPORTS`. – cjm Feb 02 '13 at 06:38
  • Now I could understand why `do "INCLUDES.pm"` works for this condition. because, `do` does not check whether module is already loaded. Am I right? – InnovWelt Feb 02 '13 at 06:43
  • Please check the edited question. I have given the scenario what I am trying to achieve – InnovWelt Feb 02 '13 at 06:50
  • @InnovWelt, yes, `do` executes the code in `IMPORTS.pm` every time, but `use` executes it only once per process. – cjm Feb 02 '13 at 07:18
  • to be clear, if setup correctly, even on repeated calls, `use` will still call the package's `import` method. – Joel Berger Feb 02 '13 at 13:34
3

use MyPackage is equivalent to BEGIN{ require MyPackage; MyPackage->import }. Inheriting from Exporter sets up an import class method which does the function "aliasing".

The problem is that you INCLUDES modules does not re-export the modules correctly. This is important because this is the process that imports the functions into the caller namespaces. While this isn't hard to craft on your own, there is a handy module for this purpose Import::Into.

Here is an example contained within a single file, it should be easy enough to reinflate into multiple, the only important difference is in the Includes module. I have made some other superficial changes but those are more for my taste.

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 {
  print "PACKAGE1_Method1 \n";
}

package PACKAGE2;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 {
  print "PACKAGE2_Method1 \n";
}

package Includes;

use Import::Into;

# uncomment in mulitple files
#use PACKAGE1 ();  # prevent import
#use PACKAGE2 ();  # ditto

sub import {
  my $class = shift;
  my $caller = caller;

  PACKAGE1->import::into( $caller );
  PACKAGE2->import::into( $caller );
}

package Test1;
Includes->import; # in seperate files replace with `use Includes;`

Method1();

package Test2;
Includes->import; # ditto

Method1();

A real world example is the module utf8::all which makes extensive use of this mechanism to load lots of unicode stuff into the caller package.

Edit

To allow importing specific things from the Includes module, you could have it inherit from Exporter as well and craft its @EXPORT and @EXPORT_OK to do what you mean. Otherwise, you could continue with Import::Into and make something like bundles.

sub import {
  my $class  = shift;
  my $bundle = shift;

  my $caller = caller;

  if ($bundle eq 'Bundle1') {
    PACKAGE1->import::into( $caller );
    ... # other things in Bundle1
  } elsif ($bundle eq 'Bundle2') {
    PACKAGE2->import::into( $caller );
    ... # other things in Bundle2
  }
}

Then in your test modules

use Includes 'Bundle1';

In short crafting your own import method is not that hard, and every little is magical about Exporter. Once you learn about symbol table manipulation your don't need it or Import::Into, though it is a slightly more advanced topic. Here is a question I asked about it much earlier in my Perl days: Demystifying the Perl glob (*)

All that said, if object-oriented concepts of inheritance and polymorphism will do the job, you might want to investigate that route too. Here is an example of that:

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

sub Method1 {
  my $class = shift;
  print "PACKAGE1_Method1 \n";
}

sub Method2 {
  my $class = shift;
  print "PACKAGE1_Method2 \n";
}

package PACKAGE2;

# if multiple files use this
#use parent 'PACKAGE1';
# rather than
our @ISA = 'PACKAGE1';

# any methods in PACKAGE2 will override those declared in PACKAGE1 

sub Method1 {
  my $class = shift;
  print "PACKAGE2_Method1 \n";
}

package Test1;

# in seperate files need to use
#use PACKAGE2;

PACKAGE2->Method1();
PACKAGE2->Method2();

package Test2;

# ditto
#use PACKAGE1
#use PACKAGE2

PACKAGE2->Method1();
PACKAGE2->Method2();

# you can still use PACKAGE1 and get the originals
PACKAGE1->Method1();
PACKAGE1->Method2();

See now there is no Includes package and no symbols are imported into the Test* namespaces. PACKAGE2 provides Method2 because it inherits from PACKAGE1 and it does not override the method declaration with one of its own.

Community
  • 1
  • 1
Joel Berger
  • 20,180
  • 5
  • 49
  • 104
  • Hi Joel,Thanks. It works. Please check the edited question. I have given the scenario what I am trying to achieve – InnovWelt Feb 02 '13 at 06:50
  • @InnovWelt, do you understand concepts of inheritance and polymorphism? If not read up about them. If so and this still won't get you where you want, then see my edit. – Joel Berger Feb 02 '13 at 13:42
  • I do know some of the concepts of Inheritance, Including Function overriding, polymorphism. My intention is that, in Tests.pm file, the function call should be `Method1()`, It should not depend on any specific packages (like `PACKAGE2->Method1()`). Because, the situation is, **the tests will be same (including function calls). Only the Implementation of the particular function should differ based on the preference** – InnovWelt Feb 02 '13 at 14:57