http://www.perlmonks.org?node_id=964975

Learned something new yesterday that's such a basic construct of Perl that I'm embarrassed to admit it, but in case someone else out there needs a refresher or didn't know this, here goes:

The foreach (and for and map, etc) command can be used to modify the list that it is iterating over. I know. I should've known this. I don't know how many times I've wanted to modify an array and had to create my own index because I didn't realize that if I had something like:

my @x = (1..10); foreach (@x) { $_ += 100; } print "@x"; OUTPUT: 101 102 103 104 105 106 107 108 109 110

I guess I just assumed it didn't because when you declare the variable in a foreach loop, you don't think of it as a reference (because it really isn't one, but it sorta acts like one in that it represents the actual element in the list.) I had always figured that $_ was simply a copy of what was in the array, not the actual element. But apparently Perl is cooler than that. ;)

SO in this example: $element can be modified in the array.

my @x = (1..10); foreach my $element (@x) { $element += 100; } print "@x"; OUTPUT: 101 102 103 104 105 106 107 108 109 110

Anyhow, the discovery was a delighful suprise, that I came across while preparing to brief a bunch of nonPerl users on Perl. This presentation's been a great way to really fill in the many cracks I have about my understanding of basic Perl programming. (I'm up to about 60 Power point slides... poor suckers attending this meeting are going to slip into a comma... death by Powerpoint! Buwahahaha)

I also tested these loops constructs and observed they altered the loop array elements in the same way...:

map {$_ += 100} @x; (yields same output and alters @x as does...) $_ += 100 for @x; in case you wondered... :)

Replies are listed 'Best First'.
Re: Perlplexation - foreach shoulda Known
by toolic (Bishop) on Apr 13, 2012 at 19:52 UTC
Re: Perlplexation - foreach shoulda Known
by eyepopslikeamosquito (Archbishop) on Apr 14, 2012 at 11:17 UTC

    The foreach (and for and map, etc) command can be used to modify the list that it is iterating over
    I recommend the Effective Perl Programming book to you and your workmates, especially item 20, "Use foreach, map and grep as appropriate", which gives an excellent summary of when to use foreach, map and grep:
    • Use foreach to iterate read-only over each element of a list
    • Use map to create a list based on the contents of another list
    • Use foreach to modify elements of a list
    • Use grep to select elements in a list
    I've often asked new Perl programmers at work to read this item when I find them writing inappropriate C-style for loops or discover they are confused about foreach and have never even used map or grep.

    Note that Hall, McAdams and foy further caution against modifying a list via map:

    "For efficiency, $_ is actually an alias for the current element in the iteration. If you modify $_ within the transform expression of a map, you modify the input data. This is generally considered to be bad style, and -- who knows? -- you may even wind up confusing yourself this way. If you want to modify the contents of a list, use foreach."

      I've had Effective Perl Programming for a year now, and really enjoyed it, but for some reason missed the detail until just this last week... I was "whoah!" and decided to try out a bunch of examples. :)

      So are "aliases" used in other Perl commands besides these sorts of loops? Is that what $a and $b are considered to be in the sort command?

        So are "aliases" used in other Perl commands besides these sorts of loops? Is that what $a and $b are considered to be in the sort command?
        Yes and yes. Some places aliases are used:
        • $_ in foreach, map and grep
        • $a and $b in a sort block
        • $a and $b in List::Util's reduce function and List::MoreUtils's pairwise function
        • Each element of @_ for the actual arguments in a subroutine call
        • By packages importing symbols
        • By an our declaration, which creates a lexically scoped alias
        • You can explicitly create an alias via typeglobs

        See also "alias" in perlglossary and Item 118 "Access the symbol table with typeglobs" in Effective Perl Programming.

      This is generally considered to be bad style, and -- who knows? -- you may even wind up confusing yourself this way. If you want to modify the contents of a list, use foreach.
      You know, I can understand people who say "I find modifying list elements using an alias confusing", and I can understand people who say "I'm fine using all the features Perl gives me, including list aliasing". I don't know what to think of people who say "I'm fine with modifying list elements in a block, but only if the keyword is foreach, if the keyword is map, it confuses me". Those aren't the people I would want to hire.
Re: Perlplexation - foreach shoulda Known
by Riales (Hermit) on Apr 13, 2012 at 19:58 UTC

    Sort of on the same topic (but maybe a bit in the opposite direction), does anybody know if there is a special variable that keeps count of which iteration in a for loop it's on?

    Say I want to do something to every third element:

    my $i = 0; foreach my $element (@array) { do_something($element) unless ($i++ % 3); }

    Is there a special variable that tracks what $i is tracking in that example?

        Wow, cool! Thanks for pointing this out. I was always curious why something like this didn't exist--turns out it did all along!

      Is there a special variable that tracks what $i is tracking in that example?
      Curiously, this is very easy in Python:
      x = [ 'apple', 'banana', 'orange' ] for i, val in enumerate(x): print i, val
      which prints:
      0 apple 1 banana 2 orange
      and fairly easy in Ruby:
      x = [ 'apple', 'banana', 'orange' ] x.each_with_index { |val, i| print "#{i} #{val}\n" }
      and I'm sure (need to wait for moritz to show me how) it's easy in Perl 6 too (probably via Array kv and/or pairs methods?).

      I was hoping List::Util or List::MoreUtils might have something nice, but the best I could find is to use an iterator like so:

      use List::MoreUtils qw(each_array); my @x = ( 'apple', 'banana', 'orange' ); my $it = each_array( @{[0..$#x]}, @x ); while ( my ($i, $val) = $it->() ) { print "$i $val\n"; }
      which is horrific. Is there a better way in List::Util or List::MoreUtils that I missed?

      While I was writing this, chromatic showed how to do it in Perl 5.12 or above:

      my @x = ( 'apple', 'banana', 'orange' ); while ( my ($i, $val) = each @x ) { print "$i $val\n"; }

        eyepopslikeamosquito writes:
        Curiously, this is very easy in Python...
        And Riales's task too: remember -- slices win ;-)
        for elem in some_iterable[::3]: do_something(elem)
        Then again, I'd argue that it's really not so curious given how much iteration support/use/avoidance is baked-in (iterator and sequence protocols, generators, comprehensions, slices, etc).

      There is not. If you're reading a file $. contains the file's line number. But if you're iterating over elements in an array it may make sense in some situations to iterate over the index instead:

      foreach my $idx ( 0 .. $#array ) { do_something($array[$idx]) unless $idx % 3; # Do something else that isn't filtered by iteration number. }

      In your example, it's probably clearer to iterate over the list rather than the indices, but in some cases the code becomes clearer the other way.


      Dave

      Is there a special variable that tracks what $i is tracking in that example?
      No. If you want that, use a C-style for loop. A reason not to have an special variable with an interation counter: how should that work with nested loops?

        I would think it could work the same way the $_ variable works in nested loops...

Re: Perlplexation - foreach shoulda Known
by tinita (Parson) on Apr 14, 2012 at 09:46 UTC
    I guess I just assumed it didn't because when you declare the variable in a foreach loop, you don't think of it as a reference (because it really isn't one, but it sorta acts like one in that it represents the actual element in the list.)
    It is called an alias.