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

Let's start with a piece of code:

my @list = map { do_something_complex($_); } @other_list;

In some of my code, I must be doing something to $_ accidentally somewhere. If I use the code above as-is, @other_list gets corrupted, and @list doesn't have what I want. If I localise $_, though, then everything works great. The problem is - I want to figure out where $_ is getting clobbered. A better solution, in my mind, is to localise $_ to the clobbering routine, to make it safer. But, of course, I'm using $_ all over the place - map's, foreach's, grep's, etc. And just using fgrep to find $_ is unwieldy - it's impossible to tell when I'm using $_ in an automatically localised area (such as map, grep, foreach) from a simple fgrep because I often have multi-line map's, grep's, etc. And then there is the use of @_ - fgrep picks up on $_[0], too, of course.

There has to be a better way. I just can't seem to find it. Ideas I have (which I don't know how to try) are currently limited to:

Any help would be appreciated - I'm pulling out my hair on this. Thanks.

Replies are listed 'Best First'.
Re: detecting changes in a localised variable
by TedYoung (Deacon) on Feb 18, 2005 at 22:25 UTC

    Here I tie $_ to a Complainer package that can warn/carp/croak any time something tries to assign to it.

    #!perl -l package Complainer; use Tie::Scalar; use base Tie::StdScalar; use Carp; sub STORE { # Warn out any status info you want here. carp "Tried to assign to \$_"; } sub FETCH { $_ } package main; tie $_, Complainer; $_ = 1; local $_;

    Update: Added the fetch method so that reading $_ actually works. not sure why I have to do that, Tie::StdScalar should handle that for me... but this was written in a hurry.

    Ted Young

    ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)

      Tie? That's an idea I obviously didn't have. However, when trying your code, which I am ever so grateful for, I noticed that local $_ caused the carping - which was what I was trying to avoid. Since $_ was now localised, I didn't care if it was being changed because that's kinda like a new value. Somewhere in my do_something_complex function, I'm causing a file to be loaded (only once - there's lots of caching going on). And that file load has a "while (<$fh>)" line in it to parse out the file. And so I'm getting about a bazillion carps - even after localisation.

      Hopefully this helps make my original query more clear... maybe not possible, but clear :-)

      Update: That all said, this file load was the problem. So my code is cleaner now because of this, although the general question is, I think, a useful question - how to determine a variable changing, discounting any localised changes. In other words, a variable whose change will propogate back up the call stack.

        Yes, I was about to mention, as I was reading your post:

        while (<FH>) {

        does not localize $_

        Ted Young

        ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)
Re: detecting changes in a localised variable
by borisz (Canon) on Feb 18, 2005 at 23:25 UTC
    Try this:
    perl -MO=Lint,dollar-underscore script.pl
    Boris
      One caveat that I saw in the perldoc for B::Lint:
      This module doesn't work correctly on thread-enabled perls.

      thor

      Feel the white light, the light within
      Be your own disciple, fan the sparks of will
      For all of us waiting, your kingdom will come

      I looked at all the B::Lint documentation which O uses... and they look interesting and useful, just not for this. The dollar-underscore option doesn't handle writes to $_, while the implicit-write will complain about all writes to $_ - which, given that I want to write to $_ (without affecting the caller), will give me many false positives. Thanks, though - it's still quite interesting and informative.

Re: detecting changes in a localised variable
by gaal (Parson) on Feb 19, 2005 at 08:10 UTC
    As TedYoung points out, while (<>) doesn't localize $_ and this is likely where it is getting clobberd.

    <plug>Here's a short talk on the subject.</plug> Here are some interesting comments I got about it last time it was mentioned. (Especially Aristotle's surprising remark that no Perl construct implicitly localizes $_ at all!)

      Especially Aristotle's surprising remark that no Perl construct implicitly localizes $_ at all!)

      Hmm. I understand Aristotle's point, but I think his assertion is wrong.

      P:\test>perl -le"$_='old value';@a=1..10; for(@a){ $_++ }; print;" old value

      Here, for is aliasing $_ to each of the values in @a, but it localises $_ first! Hence, after the loop, $_ regains it's former value.

      Perl could implicitly localise $_ for the duration of a while loop in the same way giving this:

      P:\test>type junk.dat|perl -e"$_='old value';{local$_; while(<>){print +};}print;" line 1 line 2 line 3; old value

      instead of this:

      P:\test>type junk.dat|perl -e"$_='old value';while(<>){ print};print;" line 1 line 2 line 3;

      As for why it doesn't, that's a question for those with the historical perspective.


      Examine what is said, not who speaks.
      Silence betokens consent.
      Love the truth but pardon error.

      That's a very interesting thread - thank you. I kinda got most of this, although not so clearly. The problem is that not all my co-workers get this, so I sometimes spend time dealing with the ramifications of their code :-) And this was somewhat of the problem here. (The problem was that the writer of the code in question didn't know the ramifications ... and that was me, because it was the first piece of perl code I had ever, ever written - 3+ years old, and in bad need of replacing. My co-workers still don't know the ramifications, however we now have coding standards that bypass the ramifications: while(<FH>) isn't allowed, and $_ should be avoided where it can. map, grep, etc., cannot avoid it, so go ahead there.)

        It's always impossible to know things before you learn them :)

        In the case of $_, the rule of thumb is to treat it as a global that will pollute calling code except where you specifically know it won't, e.g. foreach, map, and grep.

        In 5.9.1, you can lexicalize $_.