Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to localize an outside package variable through a lexical binding in Perl

It's a long title, but I'm afraid I can't take a single word out without losing the true meaning of the question. I'll give a quick description of what I'm trying to achieve first, then a long rambling on why I want it done this way. Skip the rambling if you intend to directly answer abiding with the question title :-)

Quick description

Assuming the existence of a lexicalize builtin that does just what I want, here goes:

package Whatever;
{
  lexicalize $Other::Package::var;
  local $var = 42;
  Other::Package->do_stuff; # depends on that variable internally
}

Whys and whynots

For a bit of context, I'm whitebox-testing a third-party module.

I want the variable localized because I want it changed for a limited time only, before the tests move on to something else. local is the best way I found to do this without depending on knowing the module's choice for an initial value.

I want a lexical binding for two main reasons:

  1. I don't want to pollute the test file's higher-level namespace.
  2. I want something shorter than the fully-qualified name. It's not in the sample code for brevity, but I use the identifier a lot more than what's shown, with calculations and updates relative to its previous value.

I couldn't decently use our because it won't grab a variable from another package: “No package name allowed for variable $Other::Package::var in "our".” Cheating to temporarily enter Other::Package's scope doesn't cut it: if I use a block ({ package Other::Package; our $var }) then the binding doesn't last long enough to be useful; and if I don't (package Other::Package; our @var; package main) then I need to know and copy the previous package's name, which prevents moving that piece of code around too much.

While doing my homework before asking a previous form of this question, I discovered Lexical::Var, which seemed like it would be exactly what I needed. Alas: “Can't localize through a reference.”

I've also tried my best on my gut feeling of my *var-based forms, but kept bumping into syntax errors. I've learned more than I cared to about scoping and binding today :-)

I can't think of a reason why what I want shouldn't be possible, but I can't find the right incantation. Am I asking for an unfortunate unimplemented edge case?

like image 540
JB. Avatar asked Oct 17 '11 20:10

JB.


2 Answers

If I get you right, all you need is our. Forgive me if I dont.

Here's working example:

#!/usr/bin/env perl
use strict;
use warnings;

package Some;

our $var = 42;
sub do_stuff { return $var }

package main;

{
    local $Some::var = 43;
    print "Localized: " . Some->do_stuff() . "\n";
};

print "Normal: " . Some->do_stuff() . "\n";

But if you try to localize private (my) variable of some package, you probably doing something wrong.

like image 145
yko Avatar answered Nov 14 '22 22:11

yko


there are several ways to do this:

  • given:

    {package Test::Pkg;
        our $var = 'init';
        sub method {print "Test::Pkg::var = $var\n"}
    }
    
  • alias a package variable from the current package to the target package.

    {
        package main;
        our $var;
    
        Test::Pkg->method; #     Test::Pkg::var = init
    
        local *var = \local $Test::Pkg::var;  
            # alias $var to a localized $Test::Pkg::var
    
        $var = 'new value';
    
        Test::Pkg->method; #    Test::Pkg::var = new value
    }
    
    Test::Pkg->method;     #    Test::Pkg::var = init
    
  • localize through a glob reference:

    {
        package main;
    
        my $var = \*Test::Pkg::var;  # globref to target the local
    
        Test::Pkg->method; #     Test::Pkg::var = init
    
        local *$var = \'new value';  # fills the scalar slot of the glob
    
        Test::Pkg->method; #    Test::Pkg::var = new value
    }
    
    Test::Pkg->method;     #    Test::Pkg::var = init
    
  • bind a the lexical in the target package, and let it spill into the current package:

    {
        package Test::Pkg;  # in the target package
        our $var;           # create the lexical name $var for $Test::Pkg::var
    
        package main;       # enter main package, lexical still in scope
    
        Test::Pkg->method; #     Test::Pkg::var = init
    
        local $var = 'new value';
    
        Test::Pkg->method; #    Test::Pkg::var = new value
    }
    
    Test::Pkg->method;     #    Test::Pkg::var = init
    

the last example uses a slightly awkward double package declaration to create a lexical in one package and get it into another. This trick is useful with local in other scenarios:

 no strict 'refs';
 local ${'Some::Pkg::'.$name} = 'something';
 use strict 'refs';
like image 30
Eric Strom Avatar answered Nov 14 '22 23:11

Eric Strom