2

Assume I have a module named Local that exports a subroutine subLocal via the %EXPORT_TAGS interface.

This module is closely related to another module named Remote that defines subroutines the user of Local might want to import.

There are two requirements I would like to have:

  1. Module Local should import the subroutines defined in Remote only if the user of module Local is importing a subroutine defined in Remote (either by explicitly naming the export or by using a specific export tag)

  2. When a subroutine from Remote is imported into Local, the user of module Local should be able to refer to that subroutine as if it is in his local namespace (the same behavior you would get when referring to a subroutine defined in Local).

I've only found a (hacky) solution for req. 2 by adding an entry in the symbol table, but this always occurs -- regardless if the user of Local actually needs the subroutines in Remote. According to perldoc, this pointlessly "pollutes" the namespace.

So at what point during compilation or runtime should I be trying to import the subroutines from Remote? And how do I actually import them in such a way that they appear in the local namespace?

This is my current approach. Module Local:

package Local;

use strict;
use warnings;

BEGIN
{
  require Exporter;

  our @ISA = qw| Exporter |;

  our @EXPORT_LOCAL  = qw| subLocal |; 
  our @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK   = ( @EXPORT_LOCAL, @EXPORT_REMOTE );
  our %EXPORT_TAGS = 
    ( all => \@EXPORT_OK, local => \@EXPORT_LOCAL, remote => \@EXPORT_REMOTE );

  *subRemote  = \&Remote::subRemote; # <-- can I do this conditionally somewhere? 
                                     # <-- and is there a better way to put this function in the user's local namespace?
}

use Remote; # <-- can I do this conditionally somewhere?

sub subLocal { return "(local)" }

1;

And module Remote:

package Remote;

use strict;
use warnings;

BEGIN
{
  require Exporter;

  our @ISA = qw| Exporter |;

  our @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK   = ( @EXPORT_REMOTE );
  our %EXPORT_TAGS = 
    ( all => \@EXPORT_OK, remote => \@EXPORT_REMOTE );
}

sub subRemote { return "(remote)" }

1;
ardnew
  • 2,028
  • 20
  • 29
  • This sounds like a circular definition to me. It reads as if you want to **1)** Import into `Local` the identifiers that are imported by `main` from `Remote` and **2)** Import into `main` the identifiers that are imported by `Local` from `Remote`. Is that correct? If so then it sounds like terrible design, and I think you need to give an example. – Borodin May 01 '17 at 18:21
  • You probably want to implement your own `import`. – Sinan Ünür May 01 '17 at 18:22
  • You won't be able to use Exporter. You'll need to implement your own `import`, or maybe one of the other export management modules can help. – ikegami May 01 '17 at 18:31
  • @Borodin no, I'm only trying to achieve your statement **2)**. but with the added requirement of making that import conditional – ardnew May 01 '17 at 18:42
  • @ardnew: Conditional on what? And what was your item **1** about? Don't you need that? A well-written example really would help hugely. – Borodin May 01 '17 at 18:42
  • @ardnew: It really sounds like your `Local` should be a *class* that *subclasses* `Remote`. That way you don't need to worry about any imports at all, and Perl will find the method wherever it is defined. – Borodin May 01 '17 at 18:46
  • @Borodin the condition being "either by explicitly naming the export or by using a specific export tag". e.g. if the user writes `use Local qw| subRemote |;`, then `subRemote` (which is defined in `Remote`) should be imported and referenced as simply as `subRemote` instead of `Remote::subRemote` – ardnew May 01 '17 at 18:46
  • @ardnew: Okay, firstly you don't need to import `Remote::subRemote` into `Local` at all; the code in `Local` can import the symbol into `main` without there being a copy in the `Local` package. And, as I said, this is a case for object-oriented design, where Perl will work everything out for you. are you up for that? – Borodin May 01 '17 at 18:51
  • I suppose using inheritance is the proper design for this, I'm just entirely unfamiliar with OO in perl. I want to play around with a custom `sub import` first to see if I can achieve what I'm intending, sorry for the confusion – ardnew May 01 '17 at 18:54
  • 1
    To be honest, I think the best solution of all for you is to forget about polluting your namespaces! You aren't planning on having hundreds of subroutines are you? – Borodin May 01 '17 at 19:20

3 Answers3

3

Why would you want to import subs into Local subs that Local is asked to export? Might as well place them directly into the right module instead of Local!

Either way, you won't be able to use (just) Exporter. There might an existing alternative to Exporter you could use. Otherwise, you'll need to write your own import.

Local.pm:

package Local;

use strict;
use warnings;

use Carp         qw( croak );
use Exporter     qw( );
use Import::Into qw( );
use Remote       qw( );

my @export_ok_local  = qw( subLocal );
my @export_ok_remote = qw( subRemote );
my @export_ok_all    = ( @export_ok_local, @export_ok_remote );

my %export_tags = (
   ':ALL'     => \@export_ok_all,
   ':DEFAULT' => [],
   ':local'   => \@export_ok_local,
   ':remote'  => \@export_ok_remote,
);

our @EXPORT_OK = @export_ok_local;

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

   my @imports =
      map {
         !/^:/
            ? $_
            : !$export_tags{$_}
               ? croak("\"$_\" isn't a recognized tag")
               : @{ $export_tags{$_} }
      }
         @_;

   my %imports = map { $_ => 1 } @imports;

   my @local  = grep { $imports{$_} } @export_ok_local;
   my @remote = grep { $imports{$_} } @export_ok_remote;

   delete @imports{ @local, @remote };
   my @unknown = keys(%imports);
   croak("Not exported by ".__PACKAGE__.": @unknown\n") if @unknown;

   Remote->import::into($target, @remote);

   @_ = ( $class, @local );
   goto &Exporter::import;
}

sub subLocal { print("subLocal\n"); }

1;

Remote.pm:

package Remote;

use strict;
use warnings;

use Exporter qw( import );

our @EXPORT_OK = qw( subRemote );

sub subRemote { print("subRemote\n"); }

1;

Test:

$ perl -e'
    use Local qw( subLocal subRemote );
    subLocal();
    subRemote();
'
subLocal
subRemote

$ perl -e'
    use Local qw( :ALL );
    subLocal();
    subRemote();
'
subLocal
subRemote

It's far simpler to simply import everything you want to export.

package Local;

use strict;
use warnings;

use Exporter qw( import );    

my ( @EXPORT_LOCAL, @EXPORT_REMOTE );
BEGIN {
  @EXPORT_LOCAL  = qw| subLocal |; 
  @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK = ( @EXPORT_LOCAL, @EXPORT_REMOTE );

  our %EXPORT_TAGS = (
    ALL    => \@EXPORT_OK,
    local  => \@EXPORT_LOCAL,
    remote => \@EXPORT_REMOTE,
  );
}

use Remote @EXPORT_REMOTE;

sub subLocal { ... }

1;
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • this is absolutely what I was imagining in my head, and probably answers the question best, but I think I have to agree with everyone suggesting it's a bit of a silly design – ardnew May 01 '17 at 19:26
  • Not at all. It's common for modules to export stuff found in other modules. It's just that go about it by importing it first. Then it becomes a trivial use of Exporter (as shown at the bottom of my answer). – ikegami May 01 '17 at 19:30
2

To be honest I think that the confusion you will create by messing with import is probably more of a problem than the namespace pollution in the first place, which is only an issue if you choose identifiers that clash with the imported ones

Here's an example using object-oriented design, which doesn't use import at all and there is zero namespace pollution. You don't even have to say in the main program which methods you will use

Remote.pm

use 5.010;

package Remote;

sub new {
    my $class = shift;

    my $self = bless {}, $class;
}

sub subRemote {
    say "I am subRemote";
}

1;

Local.pm

use 5.010;

package Local;

use base 'Remote';

sub new {
  my $class = shift;

  my $self = $class->SUPER::new(@_);
}

sub subLocal {
    say "I am subLocal";
}

1;

main.pl

use 5.010;

use Local;

my $obj = Local->new;

$obj->subLocal;
$obj->subRemote;

output

I am subLocal
I am subRemote
Borodin
  • 126,100
  • 9
  • 70
  • 144
1

I take the question to be: That the caller of Local can require subRemote in its import list, but if it doesn't then the symbol isn't pushed into the caller's namespace.

I also assume that Local should not import from Remote at all unless the caller of Local requires some of Remote's subs in its import list.

Then write your own sub import. The list supplied by the caller are arguments passed to import, following the first argument which is __PACKAGE__ (in this case Local).

Then in your import you can check whether subRemote is asked for. If it is, require the package where it is defined and push its sub's full name to the caller's symbol table, otherwise not. You can establish and check any other conditions you may need.

This way Local loads Remote only if the caller of Local requires a sub from Remote.


An example for the above description

Local.pm

package Local;

use warnings;
use strict;
use Exporter qw();

our @EXPORT_OK = qw(subLocal subRemote);

sub import {
    my $class = shift;

    my $re = qr/^(?:subRemote|other)/;
    my @local_exports  = grep { !/$re/ } @_;
    my @remote_exports = grep {  /$re/ } @_;   # check both

    if (@remote_exports) {   
        no strict 'refs';
        require Remote;
        foreach my $export (@remote_exports) 
        {   
            my $to_caller = caller() . '::' . $export;

            *{ $to_caller } = \&{ 'Remote::' . $export };
        }   
    }   

    @_ = ($class, @local_exports);  # set up @_ for goto
    goto &Exporter::import;         # switch to Exporter::import
}

sub subLocal {  print "subLocal() in ", __PACKAGE__, "\n" }

1;

The references to subs from Remote that are asked for are written to the caller's symbol table. Then our import is swapped by Exporter::import for exporting the rest of the symbols, from Local. For a note on goto see this for example. A few things are left out, firstly checks of received import lists.

There are no surprises with main and Remote

main.pl

use warnings;
use strict;
use Local qw(subLocal subRemote);

subLocal();
subRemote();

Remote.pm

package Remote;   
use warnings;
use strict;
use Exporter qw(import);

our @EXPORT_OK = qw(subRemote);

sub subRemote { print "subRemote() in ", __PACKAGE__, "\n" }

with the output

subLocal() in Local
subRemote() in Remote

This accomplishes what is asked but it has to deal with rather specific details.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • and pushing it into the caller's symbol table is done exactly how I'm currently doing it (i.e. `*subRemote = \&Remote::subRemote;`) ? – ardnew May 01 '17 at 18:33
  • @ardnew Well, right -- but when you write your own `import` then you can do that _conditionally_. Is that what you want? Completed that statement in the answer. Btw, you aren't exporting into the _caller_'s namespace.. – zdim May 01 '17 at 18:34
  • @ardnew, No, that imports into the active namespace. It doesn't export into the caller's namespace. Besides, who do you think BEGIN's caller is?! – ikegami May 01 '17 at 18:35
  • *"the caller of Local can require subRemote in its import list, but if it doesn't that the symbol then isn't pushed into the caller's namespace"* That sounds like the ordinary behaviour of `use` to me. – Borodin May 01 '17 at 18:36
  • 1
    @ardnew: I wish you'd explain a little more. Everybody's guessing what you mean. It's beginning to sound like all you need is [`export_to_level`](http://search.cpan.org/~toddr/Exporter-5.72/lib/Exporter.pm#Exporting_Without_Using_Exporter%27s_import_Method) – Borodin May 01 '17 at 18:39
  • @Borodin Ugh, right, thank you. I mean really to say that they need to implement their own `import` -- but I am not sure of what is needed in the question. (Ammended that sentence) – zdim May 01 '17 at 18:40
  • Are you thinking that the OP wants to import `Remote::subRemote` by writing `use Local 'subRemote'`? I'm still baffled. – Borodin May 01 '17 at 18:42
  • @Borodin That's my first quess, yes. I am also thinking that they have in mind some other conditions, for which they need their own `import`. Perhaps not load from `Remote::` if not required by `main::`? I am hoping for a clarification.. – zdim May 01 '17 at 18:43
  • @zdim: Yeah, I suspect they're thinking that `Remote::subRemote` has to be imported as `Local::subRemote` before it can be imported again into `main`. Okay, then that's how object-oriented inheritance works. The OP should do it that way instead! – Borodin May 01 '17 at 18:45
  • @Borodin Right, that'd be a nice solutuion. With my current understanding of the requirement, the import-way is simple as well, updated – zdim May 01 '17 at 19:01
  • @zdim: I think it's less simple to write. You have to check whether something in the import list is in `@Remote::EXPORT`, `@Remote::EXPORT_OK`, or `%Remote::EXPORT_TAGS`, and do the right thing in each case, handling collisions and symbols that aren't defined anywhere. – Borodin May 01 '17 at 19:11
  • @Borodin I do agree with that. Just don't know whether they are up for inheritance & Co. – zdim May 01 '17 at 19:17
  • @zdim: I think ikegami's solution proves my point! And I think the object-oriented route could be very simple as there's no need for any data in the object. – Borodin May 01 '17 at 19:18
  • 1
    @ardnew I updated the answer to more precisely state what I now think you need. – zdim May 01 '17 at 19:22
  • 1
    @ardnew You got really nice answers, but I realize that I misread your first comment, of how to write to caller's symbol table. There is far, far more to do than just this line, but the basics: `my $to_caller = caller() . '::subRemote'; *{ $to_caller } = \&Remote::subRemote;`. The RHS takes a reference to the sub, the LHS is the slot in the glob where it's written. If called from `main::` the LHS works out as `*main::subRemote = \&Remote::subRemote;`, but you need `caller` to be able to find that dynamically, and thus need the `{}` etc. Again, just for completeness.. – zdim May 02 '17 at 06:08