http://qs321.pair.com?node_id=1197072

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:

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'

Replies are listed 'Best First'.
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.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit
      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.

        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.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit

      I think the idea of a clean namespace (via namespace::autoclean or whatever) is that you don't get methods in your objects for helper subroutines that you imported in your class.

        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.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit

      First, use 5.010; is not "a global-scope enablement"; it's just as lexically-scoped as use feature qw( say ); (except for the version check, obviously).

      $ 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.

        1. If you put X inside curlies, it is no longer at global (file) scope. NSS!
        2. Requiring say to be enabled is about backwards compatibility; making that lexical, is not.

        Pointless pedantry, and wrong on all counts. (You and sundial should team up, that'd be really entertaining.)


        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.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit

      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.

        Given that I attempt to write all of my code to 5.8 compatibility

        I never use feature, but I can't live without defined-Or (//), so 5.10 is a prerequisite for anything I right. There was a defined-or patch for 5.8 for anyone still stuck there.


        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.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit
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.

    :-(

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Heh... the offending line is

      - 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'

        Thanks, but now I’m getting an error instead:

        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.)

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

A reply falls below the community's threshold of quality. You may see it by logging in.