Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

How to safely use $_ in a library function?

by perl-diddler (Hermit)
on Sep 17, 2013 at 11:51 UTC ( #1054425=perlquestion: print w/ replies, xml ) Need Help??
perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

I want to use $_ in a library function. I'm using it to pass a value to a "goto &sub" function that describe context of where the goto has come from.

I.e. -- several function names are used to invoke the same "basic" functionality of ~100 lines of code, with the only difference being the name of what it was called with.

The basic functionality needs to access things like the calling context to determine how to format the return value (i.e. inspects wantarray, for example, among other vals).

This generally works fine with the calling functions saving $_ via:
$_ = [[$_, ... (and other vals, like (caller 0)[3] to pick up function name)];

However, I ran into a rare case where I cannot assign to "$_". I tried making sure no local was in effect by passing the $_ using "our $_ = [...]", but that hit the same snag:
Modification of a read-only value attempted at ..."
The problem was[is] that about 6 levels up the call stack, something called something called something [...] that was called from a loop:

for (qw (literal1 literal2 literal3)) { ... }
Apparently, that usage sets the global '$_' to be an alias of each literal in a way that is making it "very difficult" to save and restore context -- part of which is not only the value of "$_" -- which was handled, but also the state of $_ (it being aliased to an unwriteable location) during the "for".

I rarely bumped into this problem, and only recently chased it down. The thing is, is that I'm unsure about how to efficiently save off $_ (such that it remains in pure, "portable", perl).

I thought it a bit odd that the global $_ would be set to an unwriteable location that remained, even when other subs were called. Might that not cause a host of other problems where "$_" is an implied operand?

I tried pushing the value onto the arg list, "@_" and popping it off in the target of the "goto &sub", but that caused behavior problems in other code that was previously working, so I rejected that fairly quickly.

So how can one save "$_" for use in a "called sub" that needs to save context so as to not disturb the called-from context? Or how can I check for and save the alias state and unalias so "$_" becomes useful again?

I'm assuming I'm having another "braino" moment, and forgetting something obvious, since with the global "$_" being unusable, perl tends to not work so well...

Ideas? Clue sticks appreciated...

Thanks...

Comment on How to safely use $_ in a library function?
Download Code
Re: How to safely use $_ in a library function?
by vsespb (Hermit) on Sep 17, 2013 at 12:26 UTC

      If I understand the OP’s question correctly, the programme’s structure is something like this:

      #! perl use strict; use warnings; for (qw(literal1 literal2 literal3)) { print "$_\n"; foo('wilma'); } sub foo { $_ = 'fred'; goto &bar; } sub bar { my ($name) = @_; print "foo --> name = $name, \$_ = $_\n"; }

      Unfortunately, changing the line $_ = 'fred'; to local $_ = 'fred'; doesn’t work: it does fix the syntax error, but within sub bar $_ is still literal1, literal2, and literal3 on successive iterations of the for loop. From the documentation for goto (underlining added):

      The goto-&NAME form ... exits the current subroutine (losing any changes set by local()) and immediately calls in its place the named subroutine using the current value of @_.

      The problem is that on each iteration of the for loop, global $_ is made an alias of the next item in the list. When that argument is a literal, this makes global $_ readonly, and that effect is undone only when that loop iteration completes. There doesn’t appear to be any way to undo this effect. :-(

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

        Indeed, you're right, seems this does not work with goto.
        *Bingo*.

        "There doesn’t appear to be any way to undo this effect. :-("

        sub foo { Internals::SvREADONLY($_, 0); $_ = 'fred'; goto &bar; }

        The intentionally-undocumented Internals package is part of Perl core (so much so that you don't even need to use it - it's completely internal, like parts of utf8 are) and has nothing much to do with the Internals module on CPAN.

        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: How to safely use $_ in a library function?
by hdb (Prior) on Sep 17, 2013 at 12:27 UTC

    Instead of appropriating Perl's work horse for your own purpose, why don't you call your variable $MyLibrarysGlobalCallStackInformation?

      Yep, and still localize it to make code reentrant:
      local $MyLibrarysGlobalCallStackInformation;
Re: How to safely use $_ in a library function?
by vsespb (Hermit) on Sep 17, 2013 at 12:47 UTC
    That's widely used pattern (1):
    package MyLib; our $MyLibrarysGlobalCallStackInformation; sub myfunc1 { local $MyLibrarysGlobalCallStackInformation = $somedata; myfunc2(); } sub myfunc2 { # use $MyLibrarysGlobalCallStackInformation here }
    Another approach (2) would be pass data directly to functions.
    package MyLib; sub myfunc1 { myfunc2($somedata); } sub myfunc2 { my ($somedata) = @_; # use $somedata here }
    Always use (2), if possible.
    Usually you need (1) when writing some kind of fancy DSL.
    For you case "goto &sub", you probably can hack something like this:
    package MyLib; sub myfunc1 { my (@args) = @_; return myfunc2(@args, $somedata); #instead of goto &myfunc2 } sub myfunc2 { my ($arg1, $arg2, $arg3, $somedata) = @_; if (defined $somedata) { .. # we got there from myfunc1 } }
      FWIW, saving, restoring and not interfering w/context is important, as is speed, though perhaps the call/return vs. goto isn't that much difference given other functionality, it does change the call stack and make detecting context issues in main function more complicated.

      Some of the code:

      ... use warnings; use warnings FATAL => qw(redefine closure); use Types; use Scalar::Util qw(looks_like_number); use constant CLASS => __PACKAGE__; use Want; sub _Var ($$$;$) :lvalue { # Wrkhorse code for manufc +trd Vars my ($__, $p, $vn, $wa)=@$_; # vn=varname our $_=$__; # must not trash '$_ +' my $c = ref $p || $p; my $rfv = ref $p->{$vn}; # rfv=ref(type) of var my $addrof; if ($wa && $wa eq '&#945;') { $wa=undef, $addrof=1 } my $arg = $_[0]; return $p->{$vn}=want('ASSIGN') if want(qw(LVALUE ASSIGN)); if (@_ && !$addrof) { # arg for "setter" my $rfa = ref $arg; # ref of the arg if ( !defined($p->{$vn}) or !defined $arg ){ # either undef $p->{$vn} = $arg; } elsif ($rfv eq ARRAY) { ## if type(var)==ARRAY, +1 param my ($index, $ap) = (shift, $p->{$vn}); if ($p->{':pusharray'} or ( !looks_like_number($index) || not( (defined $wa) || @_) )) { push @{$p->{$vn}}, ($index); ## convert to "push" return $index; ## return pushed val +ue } else { $p->{$vn}[$index] = $_[0] if @_; return $p->{$vn}[$index]; } } elsif ($rfv eq HASH) { my $subvar = shift; ## 1 var w/hash is is +a key $p->{$vn}{$subvar} = $_[0] if @_; ## another? =>assign +value return $p->{$vn}{$subvar}; } else { if (length $rfv && $rfv ne $rfa ) { ## incompat assignment warn P "Warning: var type redefined from \"%s\" to \"%s\"", $rfv, $rfa; } $p->{$vn} = $_[0]; ## assignment is default } } # how to return results? (belo +w) if ($rfv eq ARRAY ) { if (defined($wa)) { # arrays special $wa? @{$p->{$vn}} : $addrof ? \$p->{$vn} : $p->{$vn}; } } elsif ($rfv eq HASH ) { $p->{$vn} } elsif ($addrof) { return $p->{$vn} } else { return $p->{$vn}; } } sub varname ($) { substr $_[0], (1+rindex $_[0],':') } sub _access_maker { #{{{ my $pkg = shift; #var in $_ { my $proc = '# line ' . __LINE__ . ' "' . __FILE__ . "\"\n" . ' { use warnings;use strict; package '.$pkg.'; sub '.$_.' (;$) :lvalue { my $sav = [$_,shift, Data::Vars::varname((caller 0)[3]), wan +tarray]; our $_=$sav; goto &Data::Vars::_Var}; 1}'; eval $proc; $@ and die "Fatal error in $pkg\::Vars\::_access_maker?: $@\n"; } } ## end sub _access_maker }}}
      The "Types" module is fairly simple and has a bunch of functions to allow some ease-of-use code like:
      sub ARRAY (;*) { my $p = $_[0]; return @_ ? (ref $p && (1+index($p, '::ARRAY')? 1:0) : 'ARRAY' }
      I.e. it allows one to
      my $ptr=[]; say ARRAY if ARRAY $ptr;
      (with a definition for all the types -- which might explain some syntax issues in the _Var sub)

      Given that perl's not re-entrant with it's use of '$_' anyway, I suppose that using some "global var" to pass the name isn't going to be much worse.

      I'm not sure how local would help or work w/goto, though it would help w/they nested call case.

      Given that I pass most of my needed context from the named func to the generic, I likely no longer have a great need to not nest the calls, though conceptually (and historically), more of the the code for '_Var' was in the named function. There has been a fear of losing the caller's context (stuff accessible on call stack as well as 'wantarray' usage), but most of that may already be encapsulated.

      I had hoped that more context saving of $_ went on in perl than does, but that it doesn't, might make my concerns moot. *sigh*.

Re: How to safely use $_ in a library function?
by Anonymous Monk on Sep 17, 2013 at 15:02 UTC
    I have seen lots of code that uses $_ and to me it's mostly just a PITA, except in very small cases like one-liners. Give it a name... you'll be glad you did.
Re: How to safely use $_ in a library function?
by AnomalousMonk (Abbot) on Sep 17, 2013 at 15:26 UTC

    I think I like the basic approach of hdb here (elaborated by vsespb here) best, but if it's absolutely necessary to use  $_ come Hell or high water, maybe something like:

    >perl -wMstrict -le "for (qw(zik zok)) { print qq{for before: '$_'}; foo('wilma'); print qq{for after: '$_' \n}; } ;; sub foo { local $_ = 'fred'; sub { local $_ = 'XXX'; goto &bar; }->(@_); } ;; sub bar { my ($passed) = @_; print qq{gone from foo, passed = '$passed', \$_ = '$_'}; } " for before: 'zik' gone from foo, passed = 'wilma', $_ = 'fred' for after: 'zik' for before: 'zok' gone from foo, passed = 'wilma', $_ = 'fred' for after: 'zok'

    Note that local-ization within the calling context of goto (i.e., within the  sub { ... }) is still wiped out.

    Note also: Try:

    sub foo { for (qw(fred pebbles)) { sub { for (qw(hoo ha)) { goto &bar; } }->(@_); } }
      Regarding 1st solution: Since this code is used to implement pseudo-vars that can be initialized and checked at compile time, inserting an layer of callsub *seems* like a high penalty to pay.

      I tried using the "for" as a $_ specifier, but as soon as it was in the goto-target-func, the value was restored to the one in the outer loop.

      The Internals fix looks like the right way to solve this, as it addresses the problem. However, I solved it by making use of a global, as it's less likely to break. With the current maintainers, the perl feature set is unstable (they yanked lexical $_ out of feature status and into experimental status in 5.18, and forced in a new feature that affects everyone in 5.18 without opting in (via "use 5.18" or use feature "xxx"). Releasing incompatible features in the production version of perl demonstrates their level of commitment to stable interfaces. Heaven knows what they would do to the Internal interfaces.

      Didn't know "lvalue" was also experimental until last week -- as when I started using it, I followed its usage example in some CPAN code ... urg...

Re: How to safely use $_ in a library function?
by ikegami (Pope) on Sep 18, 2013 at 19:08 UTC

    The difference between

    sub { ...; goto &foo; }

    and

    sub { ...; &foo; }

    is that the stack is unwound before the call in the former and after the call in the latter. In other words, you are asking how to protect a variable in a lexical scope you explicitly exit before you need it.

    You can't have it both ways. Either you create a scope where $_ is protected or you destroy it, but you can't do both.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1054425]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (10)
As of 2014-10-23 17:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (126 votes), past polls