We all (that's a rough rounded estimate) know about the most important pragma modules - strict and warnings - and maybe also how to turn them on or off, or how to turn on/off a subset of the facilities provided by them. In comparison, few of us know how a pragma really works. I don't either - to know *really* well, I'd have to wade the perl source code, which I won't, for now.
This I know from the docs, if I haven't misread them: the variable $^H is used for hints to the compiler, e.g. how to behave in respect to refs, subs and vars. The hash %^H is used for hints from the compiler to the interpreter, i.e. perl's runtime. The current state of %^H at compile time is stored in the optree (another obscure thing most perl programmers don't care about, but that's what's getting run), so its state can be retrieved at runtime via (caller($level))[10].
We could use that to make the scope of imported functions from some arbitrary package purely lexical, even if that package isn't pragma aware. Here's a shot at this:
Package pragmatic:
package pragmatic;
use 5.10.0;
our $VERSION = 0.01;
our %pragmas; # pragmas currently in effect
our %masked; # masked symbols while pragma on
our %symbols; #
our $AUTOLOAD;
sub import {
shift; # discard package
return unless @_; # nothing to do
my ($mod,@args) = split " ", shift;
my @caller = caller(1);
# see if $mod is defined in $caller
my $callpkg = $caller[0];
unless (exists $symbols{$callpkg} && exists $symbols{$callpkg}->{$
+mod}) {
package pragmatic::import {
# bug! my $loadstr = @args ? "use $mod" : "use $mod qw(@args)";
my $loadstr = @args ? "use $mod qw(@args)" : "use $mod";
die $@ unless eval "$loadstr;1";
my $stash = "$callpkg\::";
for my $symbol (keys %pragmatic::import::) {
if (my $code = *{$pragmatic::import::{$symbol}}{CODE})
+{
if (${$stash}{$symbol}) {
if (ref ${$stash}{$symbol} eq 'CODE') { # vers
+ion > 5.20.2
$masked{$callpkg}->{$symbol}
= ${$stash}{$symbol};
} elsif (*{${$stash}{$symbol}}{CODE}) { # v5.2
+0.2 and lower
$masked{$callpkg}->{$symbol}
= *{${$stash}{$symbol}}{CODE};
}
}
$symbols{$callpkg}->{$mod}->{$symbol} = $code;
*{"$caller\::$symbol"} = \&{"pragmatic::$symbol"};
}
delete $pragmatic::import::{$symbol};
}
}
}
push @{$pragmas{$callpkg}}, $mod
unless grep {/^$mod$/} @{$pragmas{$callpkg}};
$^H{"$callpkg/pragma/in_effect"} = 1;
$^H{"$callpkg/$mod/in_effect"} = 1;
}
sub unimport {
shift;
my $mod = shift;
my $callpkg = (caller)[0];
if($mod) {
$^H{"$callpkg/$mod/in_effect"} = 0;
} else {
$^H{"$callpkg/pragma/in_effect"} = 0;
}
}
sub AUTOLOAD {
$AUTOLOAD =~ s/.*:://;
my ($callpkg,$file,$line,$hinthash) = (caller(0))[0..2,10];
if ($hinthash->{"$callpkg/pragma/in_effect"}) {
# look up symbol in reverse pragma chain for this package
for my $mod ( reverse @{$pragmas{$callpkg}} ) {
if (exists $symbols{$callpkg}->{$mod}) {
if (exists $symbols{$callpkg}->{$mod}->{$AUTOLOAD}) {
if ($hinthash->{"$callpkg/$mod/in_effect"}) {
goto &{$symbols{$callpkg}->{$mod}->{$AUTOLOAD}
+};
} else {
die "Undefined subroutine &$callpkg::$AUTOLOAD
+ called at $file line $line\n"
unless $masked{$callpkg}->{$AUTOLOAD};
goto &{$masked{$callpkg}->{$AUTOLOAD}};
}
}
}
}
die "Undefined subroutine &$callpkg::$AUTOLOAD called at $file
+ line $line\n";
} else {
goto &{$masked{$callpkg}->{$AUTOLOAD}};
}
}
1;
Usage example:
package Foo;
use 5.10.0;
require Exporter;
@Foo::ISA = qw(Exporter);
our @EXPORT = qw(foo);
sub foo { say "Foo::foo at line ".(caller)[2] }
1;
#!/usr/bin/perl
use 5.10.0;
sub foo { say "main::foo at line ".(caller)[2] }
use pragmatic Foo;
foo; # line 5
no pragmatic Foo;
foo; # line 7
use pragmatic Foo;
foo; # line 9
no pragmatic;
foo; # line 11
use pragmatic Foo;
foo; # line 13
__END__
Foo::foo at line 5
main::foo at line 7
Foo::foo at line 9
main::foo at line 11
Foo::foo at line 13
A module imported via pragmatic isn't imported into the caller's namespace, but into the package pragmatic::import. Each symbol from that package is inspected, and after inspection, deleted. If the CODE slot is defined, its content is stored in the hash %symbols for this caller and this package. If a symbol of that name is found in the caller, its CODE slot content is saved to the hash %masked. The caller is given a reference to the undefined subroutine of that name in package pragmatic. At runtime, this triggers AUTOLOAD, where the appropriate subroutine is called, depending on whether the pragma is in effect or not.
This works for exported subroutines only. If a package exports other glob types, they are passed into the caller's namespace, and they aren't managed by pragmatic. I wish there was a way to govern those too.
To be done yet:
- a forget(PACKAGE) method which finishes the pragmatic management and stores the stashed subroutines permanently in the caller's namespace
- ... (fill the blank)
What do you think? Bad idea? useful? Big delirium? Comments welcome.
edit: fixed some bugs
perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: RFC: pragma pragmatic
by BrowserUk (Patriarch) on Aug 09, 2017 at 08:39 UTC
|
Many moons ago, I implemented a 'say' keyword into the 5.8.something sources and offered it to p5p. Their response was that it needed to be explicitly enabled by the programmer to avoid clashes with existing code that used 'say' as a sub name. So, I implemented that. Their response was, the scoping needs to be lexical, and the mechanism for lexical scoping needs to be generic. I got the NIH message and gave up.
About 5 years later Perl finally got the say keyword with 5.10, and the use feature pragma was the mechanism to enable it's lexical scoping.
To this day, I've never seen anyone, anywhere use use feature qw[ say ]. Everyone who uses say, uses the "use 5.010;" or later global-scope enablement.
Now I've said that out loud, I'm sure that they'll be one or more pedants pop-up claiming they always use use feature qw[ say ]; and all those other things it can optionally enable -- that noone can remember what they are -- explicitly at the closest possible scope in every program and module they write, lording the virtues of clean namespaces...
but can anyone tell me the benefit of getting to the end of a programs run with a clean namespace? Do I win an environmental award or something?
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice.
Suck that fhit
| [reply] [d/l] [select] |
|
Now I've said that out loud, I'm sure that they'll be one or more pedants pop-up claiming they always use use feature qw[ say ]; and all those other things it can optionally enable -- that noone can remember what they are
Hello! That's me (the pedant).
The reason which I (albeit rarely) use feature 'say'; is that it means I don't have to remember which version of perl introduced say. As a bonus it's also self-descriptive - when I come back to such code in 6 months I don't have to think, "Why in Hades did I use 5.010; here?", although a comment would work just as well. FWIW I don't recall ever using use feature for anything other than "say", so we can agree on that part at least.
| [reply] [d/l] [select] |
|
Hello! That's me (the pedant).
:). Your reasoning is sound.
Having hankered for say enough, that I took the time to learn enough about the sources that I could add a say keyword; when it eventually came I tried to train my fingers to use it, but in the end, just gave up and went back to print and -l on the shebang line.
My point was mostly that even when people do use feature qw[ say ];, they use it at global (file) scope. I've never seen anyone use it lexically.
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice.
Suck that fhit
| [reply] [d/l] |
|
| [reply] |
|
namespace::autoclean is a module I've never had the need for.
If users of my OO modules -- and yes, I've written a few of those :) -- are diligent enough to look inside my modules and notice it has some undocumented methods imported from modules it uses, and they work out why and how to call them, that's fine by me. And if no one looks, those undocumented methods in the modules namespace, do no harm outside of it.
But then, I don't use Mooose, and I guess it needs all the help it can get. I guess it is conceivable that the presence of extra names in a modules stash could slow down specified method call lookup -- though I doubt anyone could measure the difference -- but then, I guess Moooose needs all the help it can get in that regard also. I also assume that the cleanup might return some memory to the runtime pool, though you wouldn't guess it from the size of every piece of Mooooose code I've ever seen :)
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice.
Suck that fhit
| [reply] |
|
|
|
|
|
$ perl -e'
> { use 5.010; say "foo"; }
> say "bar";
> '
String found where operator expected at -e line 3, near "say "bar""
(Do you need to predeclare say?)
syntax error at -e line 3, near "say "bar""
Execution of -e aborted due to compilation errors.
Secondly, the need for say to be enabled has nothing to do with a need for "clean namespaces"; it's all about not breaking existing programs that have a sub named say.
| [reply] [d/l] [select] |
|
| [reply] |
|
|
I too very infrequently use use feature 'say';, but it is always only used for testing. hippo had a good point for prod work if one doesn't remember which version included which feature, but I'm a bit different here. I've been reading perldelta since 5.8, so for things that stick, it's not often I forget which version contained what.
My reasoning for selecting individual 'feature's is typically because I only need one or two to make it easier to do certain things in a test file while debugging specific problems. Given that I attempt to write all of my code to 5.8 compatibility, I specifically put in the feature I need explicitly, then remove it afterwards. In other words, I add the feature temporarily, use *only* it/them, then when the debugging work is done, remove the debug/test lines and the individual features.
It avoids me from going overboard with newer features I didn't expressly use, and have to re-edit the specific file because I forgot things. If I haven't included a whole raft of features by using a whole branch, it's less likely I'll have additions I didn't intend later.
| [reply] [d/l] |
|
| [reply] |
|
Re: RFC: pragma pragmatic
by Athanasius (Archbishop) on Aug 10, 2017 at 06:48 UTC
|
Hello shmem,
This package looks interesting, but unfortunately it doesn’t seem to work on Windows. I get:
13:50 >perl -I. main.pl
main::foo at line 5
main::foo at line 7
main::foo at line 9
main::foo at line 11
main::foo at line 13
16:37 >
Tested using:
- Strawberry Perl v5.20.2 built for MSWin32-x64-multi-thread
- Strawberry Perl v5.26.0 built for MSWin32-x64-multi-thread-ld (output shown above)
- Cygwin Perl v5.22.4 built for cygwin-thread-multi
under Windows 8.1, 64-bit.
:-(
| [reply] [d/l] |
|
- my $loadstr = @args ? "use $mod" : "use $mod qw(@args)";
+ my $loadstr = @args ? "use $mod qw(@args)" : "use $mod";
Silly bug, sorry... thanks for the report! Corrected in the OP.
perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
| [reply] [d/l] |
|
20:20 >perl -I. main.pl
Not a GLOB reference at pragmatic.pm line 26.
BEGIN failed--compilation aborted at main.pl line 12.
20:20 >
(Line 12 in my main.pl file is the first occurrence of use pragmatic Foo;)
Update: That was under Strawberry Perl 5.26.0, and I get the same error with Cygwin Perl 5.22.4. But Strawberry Perl 5.20.2 seems to be working correctly:
20:26 >perl main.pl
Foo::foo at line 13
main::foo at line 16
Foo::foo at line 19
main::foo at line 22
Foo::foo at line 25
20:26 >
(Those line numbers are correct in my main.pl file.)
| [reply] [d/l] [select] |
|
|
A reply falls below the community's threshold of quality. You may see it by logging in. |
|
|