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

I had a snippet of my code that went through an array thusly:
while( shift @array ) { some_operation $_; }
which wasn't working. I switched to
foreach( @array ) { some_function $_; }
and that seemed to work fine. This surprised me. I looked up $_ in the camel book and I found that Perl will assume $_ for "various unary functions" and "various list functions" (in the section on Global Special Variables). This is rather vague. So when is $_ used and when isn't it?

Replies are listed 'Best First'.
Re: When does $_ get used?
by dragonchild (Archbishop) on Jul 26, 2001 at 21:35 UTC
    You're confusing $_ with the return from shift. shift will assume @_ if it isn't given an argument, but it will not assign its return value to $_. None of the functions will ever assign their return value to @_ or $_. If there is no-one to receive the value, the return value is discarded.

    Now, the reason that foreach(@array) works the way you think it does is because that's a property of foreach. while does not have this property. That may have been the piece of info you were missing.

(ichimunki) Re: When does $_ get used?
by ichimunki (Priest) on Jul 26, 2001 at 21:44 UTC
    This is partly a scoping issue, but mostly a void context issue. Variables declared with a foreach have a scope lexical to the associated block and in this case $_ is implied by the absence of an iterator. The variables in your while condition have no such luck. If you perform a shift operation in a void context, Perl is not going to assign a value to $_ automatically.

    #!/usr/bin/perl -w use strict; my @list = ( 1, 2, 3, 'foo', 4, 'bar' ); $_ = 1; foreach (@list) { print "$_\n"; } print "between loops: $_\n"; while (shift @list) { print "$_\n"; } print "after loops: $_\n"; shift @list; shift @list; shift @list; print "$_\n"; #we are expecting 3, but it's really 1.


    UPDATE: IMPORTANT NOTE. One thing we didn't mention here is that each time you are shifting @list, you are destroying your list, so eventually the list is completely destroyed which will cause the while condition to evaluate false and stop the while loop. But since we've destroyed the loop the final 3 shifts are actually operating on an empty list!
Re: When does $_ get used?
by wiz (Scribe) on Jul 26, 2001 at 21:50 UTC
    $_ is used if a variable is used in the function. while (shift @array) tests whether shift @array is true. It doesn't go through the values of @array.
    foreach (@array){} says go through each value of @array. $_ stands for the current element for each step in the foreach.

    $_ is used if the function in question searches through the the values of an @array or keys %hash.

    If it doens't search through the values, then it doesn't use $_.

    ----------------------------
    Wiz, The VooDoo Doll
    Head Master of 12:30 Productions
    ----------------------------
      I'm sorry I confused the issue. My question wasn't "why doesn't while assign a value to $_" but "why doesn't shift?"

      Certainly while( <> ) { ... } works as I expected it to. While talking it over with other helpful monks via the Chatterbox, I found that the individual functions will say if they use $_ in their descriptions.

      That being said, is there a good reason why shift (for example) doesn't assign a value to $_ unless another variable is explicitly used?

        Because, for one thing, it's useful to call shift in void context to remove an array element with no other side-effects. Clobbering $_ in these cases would make it less useful.
Re: When does $_ get used?
by mexnix (Pilgrim) on Jul 26, 2001 at 22:08 UTC
    I didn't really get your question at first, but after reading the replies, esp. dragonchild, I understood what was going on. If you use while (my $elm = shift @list) { some_funct $elm } (or while ($_ = shift @list) {)that will work. I doubt, have no clue, that this is coding practice, but it works. All the other replies are good, this is just using while.

    __________________________________________________
    <moviequote name="The Whole Nine Yards">
    Jimmy T: Oz, we're friends, friends do not engage in sexual congress with each others wives.
    </moviequote>

    mexnix.perlmonk.org

      It is indeed bad coding practice, unless the following behaviour is actually desired:
        
      my @l = (1, 2, 3, 0, 5, 6); while (my $x = shift @l) { print "$x "; } ## OUTPUT ## 1 2 3
      The correct idiom is:
        
      my @l = (1, 2, 3, 0, 5, 6); while (@l) { my $x = shift @l; print "$x "; }
         MeowChow                                   
                     s aamecha.s a..a\u$&owag.print
        My idea, and what I thought elbie's idea was, is treating the @list like a filehandle (i.e. the ever used while (my $line = <STDIN>) { something_with $line }). However, I submit to your [much] greater knowlegde, and vow never to do it myself.

        __________________________________________________
        <moviequote name="The Whole Nine Yards">
        Jimmy T: Oz, we're friends, friends do not engage in sexual congress with each others wives.
        </moviequote>

        mexnix.perlmonk.org