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:
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)
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;
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;