Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

A lexical lib pragma?

by perlancar (Hermit)
on Jan 04, 2020 at 03:45 UTC ( [id://11110927]=perlquestion: print w/replies, xml ) Need Help??

perlancar has asked for the wisdom of the Perl Monks concerning the following question:

Hi monks,

I wonder how hard would it be to create a lexical version of the lib pragma. Illustration:

# @INC is (a,b,c) here { use lib::lexical "d", "e"; # @INC is (d,e,a,b,c) here require Foo; { no lib::lexical "b", "c"; # @INC is (d,e,a) here require Bar; } # @INC is (d,e,a,b,c) here } # @INC is (a,b,c) here

I've read perlpragma and know about %^H, but don't know if it can help in this case. I'm also aware about modules like B::Hooks::EndOfScope, but in this case we want to execute code after the caller's end of scope.

Anyway, there's always this if the above is not possible:

# @INC is (a,b,c) here { local @INC = ("d", "e", @INC); # @INC is (d,e,a,b,c) here require Foo; { local @INC = grep { $_ ne "b" && $_ ne "c" } @INC; # @INC is (d,e,a) here require Bar; } # @INC is (d,e,a,b,c) here } # @INC is (a,b,c) here

Replies are listed 'Best First'.
Re: A lexical lib pragma?
by ikegami (Patriarch) on Jan 04, 2020 at 06:01 UTC

    Consider the following:

    sub foo { say "Pragma out of scope:"; say for @INC; } { use lib::lexical 'path'; say "Pragma in scope:"; say for @INC; foo(); }

    If the pragma modifies @INC and resets it at end of scope, foo will see the change. A hook is by far the easiest solution. Otherwise, you have to convince the parser to handle use, require and do differently than normal.

    do and require can be done using an opcode checker, but use can't be done that way because of its compile-time effect. Maybe a call_parser/call_checker would work?

    Anyway, I don't see the point of such a pragma. One a module is loaded, it stays loaded regardless of any changes to @INC, so the effect of changing @INC is global even if the change is temporary.

      Your example does show that the effect of "use lib::lexical" is actually, just like local, dynamic instead of lexical. I wouldn't say "global" as the @INC is indeed is restored at the end of the scope. The secondary effect (or tertiary and so on) can be global and/or irreversible but this is brought upon by other action (do/require/use) and not by "use lib::lexical" or the change to @INC itself. And that can be said about any lexical pragma.

      I did originally imagine lib::lexical mainly as a syntax sugar for 'local @INC = (..., @INC)'.

        The change is seen everywhere in the interpreter, and that's the very definition of global. In any case, it's definitely not lexical! The point is that changes to @INC could have very far reaching effects. The fact that the change is temporary doesn't change that.

        I did originally imagine lib::lexical mainly as a syntax sugar for 'local @INC = (..., @INC)'.

        That would be FAR clearer to the reader! (But just as dangerous and pointless.)

Re: A lexical lib pragma?
by LanX (Saint) on Jan 04, 2020 at 04:49 UTC
    many ways I could think of... for instance

    • @INC could hold a coderef as hook which checks the hint-hash %^H before returning a module ( See require for details)
    • a source filter injects your "local @INC" approach

    Filter-Inject might help you, it demonstrates both techniques

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Installing a hook in @INC would be considered "cheating", as the lib::lexical's job is to add specific entries to @INC as instructed and not install extraneous one not specified by user. But I'm still willing to consider using this technique.

      As for source filter, I'd rather not go that route.

        Installing a hook in @INC would be considered "cheating", as the lib::lexical's job is to add specific entries to @INC as instructed and not install extraneous one not specified by user.

        It is, however, the only way that I can see to make such a change to @INC truly lexical in scope, since it's a global (package) variable. In this node, you seem to be saying that dynamic instead of lexical scoping is enough? Also, it would be good if you could clarify whether you want this effect at compile time, such that it affects use, runtime (like in your example in the root node), or both?

        As for source filter, I'd rather not go that route.

        Note that LanX's Filter::Inject is different from a traditional source filter in that it only injects code, and doesn't rewrite anything, which makes it much safer to use than traditional source filters. See also his very interesting and enlightening recent talk Fun with Macros.

        How gracious!
Re: A lexical lib pragma?
by shmem (Chancellor) on Jan 04, 2020 at 16:54 UTC
    I wonder how hard would it be to create a lexical version of the lib pragma.

    Short answer: very hard, since restoring @INC from a lexical must be done at compile time. But without a BEGIN block, this lexical is set at runtime. So Padwalker maybe, or some wizardry which works on the code which is currently being compiled - if such a beast exists at all.

    I would go the route of the if module:

    use lib::lexical MODULE, PATHSPEC;

    or even

    use lib::lexical modules => MODULESPEC, path => PATHSPEC;

    without modifying @INC at all in the calling code, and leave that to the module loader lib::lexical, which localizes @INC. So, for the first usage, something like this:

    package lib::lexical; our @SAVEINC = @INC; our @LOCALINC = @INC; sub work { my $flag = shift(); my $method = $flag ? 'import' : 'unimport'; my $p = shift; # PACKAGE if (! $p and ! $flag) { # plain 'no lib::lexical;' @LOCALINC = @SAVEINC unless $p; # restore to original @INC return; } if ($flag) { # prepend @paths to @INC unshift @LOCALINC, @_; } else { # weed out @paths from @INC my %i; @i{@_} = (1) x @_; @LOCALINC = grep { ! $i{$_} } @LOCALINC; } local @INC = @LOCALINC; warn $_,$/ for @INC; (my $file = "$p.pm") =~ s!::!/!g; require $file; my $m = $p->can($method); goto &$m if $m; } sub import { shift; unshift @_, 1; goto &work } sub unimport { shift; unshift @_, 0; goto &work } 1; __END__

    Wait, what - this is (roughly) just what the lib module does? Yes of course! But I'd like to have

    no lib;

    just restore the original @INC, so lib::unimport should be:

    sub unimport { shift; @INC = @ORIG_INC and return unless @_; # patch for the lazy <--- H +ERE my %names; foreach my $path (@_) { my($arch_auto_dir, $arch_dir, $version_dir, $version_arch_dir) = _get_dirs($path); ++$names{$path}; ++$names{$arch_dir} if -d $arch_auto_dir; ++$names{$version_dir} if -d $version_dir; ++$names{$version_arch_dir} if -d $version_arch_dir; } # Remove ALL instances of each named directory. @INC = grep { !exists $names{$_} } @INC; return; }

    And then, the lib module modifies @INC globally, whereas the above module just uses a modified @INC for one use call (and the use calls which this call triggers)/.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      Thanks for this! Will study this further.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11110927]
Approved by LanX
Front-paged by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (2)
As of 2024-04-25 22:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found