Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

RFC: pragma pragmatic

by shmem (Chancellor)
on Aug 09, 2017 at 07:55 UTC ( #1197072=perlmeditation: print w/replies, xml ) Need Help??

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'

Replies are listed 'Best First'.
Re: RFC: pragma pragmatic
by BrowserUk (Pope) 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 (Chancellor) 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,

Re: RFC: pragma pragmatic
by sundialsvc4 (Abbot) on Aug 09, 2017 at 16:41 UTC

    It may well be that something like use feature say would be regarded as a rabbit-hole, unless great care were to be taken to ensure that every single one of them was wholly independent of the other, such that they could be specified or omitted in any order or combination.   Then there would always be the niggling question of what a particular feature-keyword did or did not include.   Maybe that is why use version_number appears to be the use-case of choice.

    Perhaps it is merely that I have not personally encountered the problem that this package is intended to solve, but my initial reaction to it is clouded by the fact that I really don’t know what problem it is intended to solve, and I would therefore feel uncomfortable seeing it in some new-to-me and likely-broken piece of source code that I was obliged to deal with.   (Which, in my particular line of work, happens very frequently.)   What, exactly, is the “irresistible return-on-investment” of this fee-chur?   Said in a sentence or three that any Monk could instantly understand.   In particular, how does this feature take Perl out of the main-stream of behavior that I am already conditioned to expect?

      It may well be that something like use feature say would be regarded as a rabbit-hole, unless great care were to be taken to ensure that every single one of them was wholly independent of the other, such that they could be specified or omitted in any order or combination. Then there would always be the niggling question of what a particular feature-keyword did or did not include. Maybe that is why use version_number appears to be the use-case of choice.

      As far as I know, great care has been taken, and each feature controlled by feature is independent. Please read the resource the former link points to.

      Said in a sentence or three that any Monk could instantly understand.

      This sentence is written in the third paragraph in the OP: "make the scope of imported functions from some arbitrary package purely lexical, even if that package isn't pragma aware."

      That is: implement the facitlity for turning on/off the access to imported subroutines in the same way in which features of strict and warnings can be turned on and off in a lexical scope.

      If that explanation is still unclear, don't hesitate to ask.

      In particular, how does this feature take Perl out of the main-stream of behavior that I am already conditioned to expect?

      I'm not sure what you mean by "main-stream of behavior". I am reading that as "documented behavior". This feature doesn't take perl out of that, it doesn't add bizarre behavior or undocumented magic. It just combines already well documented features (or "perl behavior") into a new, simple to use, package, which provides something currently not available easily: limiting the scope of imported subroutines/functions to a lexical scope. Object oriented methods aren't affected, since the existence of the package in perls symbol table stash is not altered in any way.

      perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'

        Well, I am of course fully aware of the previous postings, and (in fact) of the technical contexts in which they were made ... (gasp!   down-voters, it is true!), but I suppose that the locus of my concern understands all of that.   (And, before you reflexively down-vote this post, shaddup and hear me out ...)

        I consider pragmas such as use strict and use warnings to be completely unrelated(!) to situations such as this, even though the same syntactic-keyword, use, is “used” in both cases.

        To my way of thinking, constructs such as use strict and use warnings are nothing more and nothing less than “ways to flip compiler-switches, which are considered only ‘at compile-time.’”   They are strictly applied at compile time, to the limited extent that languages such as Perl possess a concept of “compile time,” and specifically do not influence the run-time behavior of the code.   Therefore, to my way of thinking, only(!!) these things qualify as “lexical.”

        Per contra, the features that you describe ... (IMHO!) ... do not.

        At the very moment that you, as you say, “make the scope of imported functions from some arbitrary package purely lexical,” or, “limited the scope of imported subroutines/functions to <anything_at_all>,” I would stress that (IMHO) you (of course, whoever you are ...) have just now called upon the language system to impose your own view of things upon the (unknown and unknowable) prerogatives of the original programmer of that “arbitrary” package.

        Truth be said (IMHO!), you have just done something that said original programmer not only did not anticipate, but could not have anticipated.   I would argue that you have co-opted that programmer’s intention, which, I would also (respectfully!) argue, is something that you must not do.   Because you can be assured of success only in limited ... and, well-known to you ... cases.   Maybe it is no big deal.   Maybe it is huge.   (And, who’s to know?   It worked for you.   Goody for you.)

        Please, please, understand the point-of-view from which I am coming:   unlike most of the apparently-present population of PerlMonks, I am never the original programmer.   The original-programmers quite likely have either been fired, have quit, or have been laid off, or have been sent back to the foreign-countries from whence they came.   To my way of thinking, the things that you describe are not “lexical,” because they are not in fact of such a nature that can be objectively resolved, entirely by the compiler, “at compile time.”   I can’t look at your source code and be certain what it means.   I might potentially have to guess what version of a (highly-volatile, say) CPAN package you might have been considering at the time that you decided, “okay, this works.™”

        (Mind you, I am not there to judge you.   After all, why should I care ...?)   But you have imposed upon me an “out-of-band consideration,” of very-large and very-uncertain potential impact, which I am now (very unpleasantly) forced to consider.   Thanks a lot.)

        Hence, such matters have become something that is near and dear to my ... career.   To me, your idea introduces into the source-code many side-effects that are difficult if not impossible to discern from any simple reading of the source-code alone.   (Which is all that I ever have to work with.)   Instead, the “proper interpretation” of “what I see in front of my face” is now made dependent upon the inner workings of “an arbitrary package.”   (And, what version of that thing might you have used ...?   Oh, dear.)

        I freely grant that my point-of-view might be considered to be “an edge case.”   I have become accustomed to that, also.   But I submit, all-too defensively now, that it is valid.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://1197072]
Front-paged by Arunbear
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (7)
As of 2017-08-24 10:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Who is your favorite scientist and why?



























    Results (367 votes). Check out past polls.

    Notices?