Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Default variable $_ is not getting overwritten

by fireblood (Acolyte)
on Jul 03, 2016 at 17:09 UTC ( #1167076=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks, when I first learned perl I read that one needs to be careful to avoid creating $_ in nested loops because the inner one will clobber the outer one. But on a whim I just created a program called x which contains the following and found that it WORKS!

for (<>) { print "Current line is $_"; for (split) { print "Current token is $_\n"; } }

When I type cat file | x, each line from file is first displayed and then each token within that line is displayed immediately afterwards. I would have thought that the outer "for" would create a list of values that are popped off and assigned to $_ one at time for each pass of the outer "for" loop, but that the inner "for" would replace that list with a list of its own. But that is not happening, the outer "for" is working fine as though the inner "for" was not there. It looks as if each execution of the inner "for" creates and then exhausts its own list whose values it assigns to its own $_ and that this has no effect on the list and the $_ that the outer "for" uses. I could understand this if there were a local $_ within the first pair of braces, but that's not the case here.

Before I start relying on this behavior in production programs that I write I wanted to seek your wisdom in confirming that this is expected perl behavior that I can reliably count on. Thank you!

Replies are listed 'Best First'.
Re: Default variable $_ is not getting overwritten
by haukex (Chancellor) on Jul 03, 2016 at 18:02 UTC

    Hi fireblood,

    ... the inner "for" would replace that list with a list of its own

    This is the bit of logic that choroba already pointed out is the mistake: the outer for sets a local $_ to each of the lines of input, while the inner for sets a local $_ to each return value of the split (which is only called once on each iteration of the outer loop, and it splits the outer loop's $_, i.e. the line).

    See Foreach Loops (emphasis mine):

    The foreach loop iterates over a normal list value and sets the scalar variable VAR to be each element of the list in turn. If the variable is preceded with the keyword my, then it is lexically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with my, it uses that variable instead of the global one, but it's still localized to the loop. This implicit localization occurs only in a foreach loop. ... If VAR is omitted, $_ is set to each value.

    I acutally wanted to pick up on something else:

    Before I start relying on this behavior in production programs

    I would be very careful with nested loops setting multiple versions of $_ in production programs. It's incredibly easy to introduce a bug by just moving a line around. My view on this is not as strict as some other peoples' that I have seen, who say to avoid $_ as much as possible and use named lexicals everywhere. I think that for short loops of maybe 1-3 lines of code, using $_ is usually fine. However, nested loops usually exceed that limit and so in the example you've posted, I'd recommend you use named lexicals in both your loops.

    Hope this helps,
    -- Hauke D

Re: Default variable $_ is not getting overwritten
by choroba (Archbishop) on Jul 03, 2016 at 17:25 UTC
    Why should the inner "for" replace the list the outer "for" created? That's not what $_ contains, it only contains one line from the input.

    But even moving the print "Current line is $_"; after the inner "for" still works, i.e. the whole line is printed, not just its last entry. That's because the "foreach" loop localizes its loop variable (see Foreach Loops).

    BTW, one usually uses the diamond operator with while rather than for .

    Update: See $_ getting clobbered by inner loop. for an example where the problem really happens.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Default variable $_ is not getting overwritten
by GrandFather (Sage) on Jul 03, 2016 at 21:30 UTC

    Regardless of how $_ behaves, not using explicit named loop variables is bad coding practice. Well named variables document your code and make it easier to understand, both by you now as you write and debug the code, and by a future stranger with no idea of how the code works (likely a future you) who has to maintain the code.

    Do the smart thing, use named variables. You'll thank yourself for it in the future.

    Premature optimization is the root of all job security

      Whilst I would generally agree, there's a few places where I consider using $_ to be reasonable. They're mostly the places where I'm not using it explicitly.

      So something like:

      while ( <$input> ) { chomp; my @row = split; #stuff }

      I think is fine, because you _are_ naming it - but still using $_ to preprocess. Likewise sometimes a for loop of the form:

      $_ -> methodcall() for @objects;

      I'm increasingly starting to appreciate it - and as a rule of thumb, if I'm actually _writing_ $_ outside of a single line command (e.g. map/grep/for) then I should be using a named variable.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (9)
As of 2020-03-30 13:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    To "Disagree to disagree" means to:









    Results (175 votes). Check out past polls.

    Notices?