http://qs321.pair.com?node_id=285359

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

Why does this work:
foreach my $item ($name,$nerd,$noodle,$froodle,) { $item =~ s/$i_seek/ /g; }
But this not?
my @look_for = ($name,$nerd,$noodle,$froodle,); foreach my $item (@look_for) { $item =~ s/$i_seek/ /g;
It's all very confusing!!

Replies are listed 'Best First'.
Re: array confusion
by esh (Pilgrim) on Aug 21, 2003 at 06:43 UTC

    In loops like this, Perl does some magic that other popular programming languages generally do not. For folks who come from other languages or are not familiar with this magic, it can be surprising, and often in negative ways.

    You might normally think that the value of each element in the list is copied to the loop variable ($item in this case). In fact, the loop variable becomes a synonym for the elements of the list. This means that if you modify the loop variable, it will modify the object inside the list to which it is currently referencing.

    To be even more specific, this only applies when the list element currently being processed is an "lvalue" (fancy language for "something that can be modified"). So, if the code is

    foreach my $item ($a, 2, $c) { $item = 1; }
    then $a and $c will be set to 1, but the constant "2" will be copied into $item, $item will be changed to 1 and the constant "2" will be left alone.

    This is why your first code sample works the way that you want it to. You have listed the actual variables you want to modify inside the foreach loop.

    In your second sample, you are first copying the values in your variables into a new array and using that array in the loop. Now, you're modifying the elements of the array inside the loop and not the original variables.

    Another place where this type of synonym behavior works is in calls to subroutines. The elements of @_ are actually referencing the variables of the caller's parameters (where they are lvalues). Most of the time we tend to copy @_ elements into local variables; modifying those will not modify the caller's data.

    But I ramble.

    -- Eric Hammond

      Following on from the foregoing to achieve a result from the second example the same as the first you could do:
      my @look_for = ($name,$nerd,$noodle,$froodle,); foreach my $item (@look_for) { $item =~ s/$i_seek/ /g; } ($name,$nerd,$noodle,$froodle,) = @look_for;

      But this looks a little clumsy - maybe there's a better way?

        That is one way. It assigns the list of values contained in @look_for back to the original scalar variables.

        Another way is using references.

        # Assign to @look_for, references to $name, etc. # Note: \($x, $y, $z) is the same as (\$x, \$y, \$z) # because the reference symbol binds to each element in # the list. my @look_for = \( $name, $nerd, $noodle, $froodle,); foreach my $item ( @look_for ) { # notice how I dereference $item so that we get # to what it points to. $$item =~ s/$i_seek/ /g; }

        That will do what he's looking for, unless, of course, we've now broken other uses of @look_for in his code where his code might be expecting @look_for to contain scalar values rather than references to scalar variables.

        Dave

        "If I had my life to do over again, I'd be a plumber." -- Albert Einstein

      And the flickering light finally becomes established. Thanks for that clear and erudite explanation.
Re: array confusion
by shenme (Priest) on Aug 21, 2003 at 06:34 UTC
    Not sure what you might've been expecting? I threw lots of prints at your code:
    $pattern = '[aeiou]'; ( $s1, $s2, $s3, $s4 ) = qw( name nerd noodle froodle ); printf "was: '%s'\n", join("' '",$s1,$s2,$s3,$s4); foreach my $item ($s1,$s2,$s3,$s4,) { $item =~ s/$pattern/ /g; } printf "now: '%s'\n", join("' '",$s1,$s2,$s3,$s4); ( $s1, $s2, $s3, $s4 ) = qw( name nerd noodle froodle ); @a = ( $s1, $s2, $s3, $s4 ); print "\n"; printf "was: '%s'\n", join("' '",$s1,$s2,$s3,$s4); printf "was: '%s'\n", join("' '",@a); foreach my $item (@a) { $item =~ s/$pattern/ /g; } printf "now: '%s'\n", join("' '",$s1,$s2,$s3,$s4); printf "now: '%s'\n", join("' '",@a);
    and got
      was:  'name'   'nerd'   'noodle'   'froodle'
      now:  'n m '   'n rd'   'n  dl '   'fr  dl '
    
      was:  'name'   'nerd'   'noodle'   'froodle'
      was:  'name'   'nerd'   'noodle'   'froodle'
      now:  'name'   'nerd'   'noodle'   'froodle'
      now:  'n m '   'n rd'   'n  dl '   'fr  dl '
    
    When you did
    my @look_for = ($name,$nerd,$noodle,$froodle);
    you copied the _values_ in each variable into the array. You then looped through the array elements changing their values through the magic of foreach aliasing. But that doesn't change the variables you copied from.
      Ah, a light begins to flicker and become brighter. This also explains BUU's contribution, which I didn't get the first time round.

      Thanks one and all for your help

      Phil
Re: array confusion
by davido (Cardinal) on Aug 21, 2003 at 07:37 UTC
    Actually it's not too difficult to fix your problem, and I can point out the issue along the way.

    First, you should understand that in your first example, the loop is iterating over the actual scalar variables, $name, $nerd, $noodle, $froodle. And $item is aliasing (or "is a") those scalars. When you change $item, via the substitution, you change value of the variable that it represents; being $name, $nerd, $noodle, and $froodle, depending on the iteration of the loop.

    In your second example, you are assigning the values contained in the scalar variables $name, $nerd, $noodle, and $froodle, to an array named @look_for. But what that means is that @look_for just contains a list of values, not a list of scalar variables.

    So in your second example, when you iterate over each element contained in @look_for, $item becomes an alias for $look_for[0], in the first iteration, $look_for[1] in the second iteration, etc. And as you recall, those elements contain the values that were passed to them by $name, $nerd, etc. The distinction is that at this point, $name, $nerd, and so on, are no longer associated, in any way, with @look_for.

    The result is that as you change $item in the second example, you're changing the value of elements of @look_for, not the value of the variables $name, and so on.

    If you intend to use the second example, you need to give @look_for references to the scalar variables $name, $nerd, $noodle, and $froodle, instead of just the values. That way, each element of @look_for refers to the original scalar variables. Then, inside the loop, you must dereference $item to change the value of the scalar variable referenced by the array element $item aliases. Here is a working example:

    my @look_for = ( \$name, \$nerd, \$noodle, \$froodle ); foreach my $item ( @look_for ) { $$item =~ s/$i_seek/ /g; }

    Now you'll find that you're modifying the value of the original scalar variables pointed to (in C-speak) or referred to (in Perl-speak) by the elements in @look_for. Just remember to always dereference the elements in @look_for when you mean to modify the values of the variables they point to.

    I hope this is helpful. I recommend reading the perldoc on references, and on lists of lists, as both will aid you in understanding how this works.

    Dave "If I had my life to do over again, I'd be a plumber." -- Albert Einstein

Re: array confusion
by demerphq (Chancellor) on Aug 21, 2003 at 08:53 UTC

    As has already been explained for (LIST) aliases the iterator variable to the apropriate member of the list, just as @_ is actually a list of aliases to the passed parameters. (map{} and grep{} do the same thing as for incidentally, while/until does not.)

    This is easily overlooked and can result in strange things happening by oversight. For instace replace @look_for with qw(look for) and you'll get an error. Same if you replace it with (1,2,3). (The workaround is to write for (my @temp=qw(...)). You might think these examples are obvious but they can be less so when the action is at a distance when combined with the behaviour of @_:

    sub one { two(@_); } sub two { s/(.)\1/$1/ for @_ } one(qw(aa bb cc))

    One interesting and subtly obvious (once you think about it) example of where this doesn't appear to happen is when iterating over the keys of hash. In this case the iterator is not aliased to the actual key but to a copy of the key.(otherwise very odd things could happen to the hash, furthermore the keys of a hash dont live in SV's so need to be placed in one before they can be accessed in Perl land)

    If you really want @look_for to contain aliases to $name, $nerd etc, then you can do two things, use a reference to @_ to create an array ref of aliases, or use the module Array::RefElem

    sub alias_array { \@_ } my $look_for = alias_array($name,$nerd,$noodle,$froodle,); foreach my $item (@$look_for) { $item =~ s/$i_seek/ /g; }

    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: array confusion
by BUU (Prior) on Aug 21, 2003 at 06:17 UTC
    when you do  for my $foo () $foo is aliased to the var. But when you assigned the scalars to a list they were copied so when you did @arr = ($foo,$baz,$bar); for(@arr){s/stuff/cling/;} you were actually affecting $arr[0],$arr[1],$arr[2] and so on.
Re: array confusion
by hotshot (Prior) on Aug 21, 2003 at 06:10 UTC
    Your two examples are not the same try doing:
    foreach my $item (($name,$nerd,$noodle,$froodle,)) { # another brack +ets $item =~ s/$i_seek/ /g; }
    and you'll see it doesn't work either

    Hotshot
      (Please see Update below. I'm not editing out my mistakes, but I don't want to confuse readers.)

      I'm going to have to disagree with this statement. As far as I can see from the provided code, they are very close to the same.

      Furthermore, I believe that the extra set of parens in your example makes no difference in the functionality. Try this for yourself:

      perl -e 'foreach my $i ((1,2,3,4)) { print "$i\n" }'
      Update: I'm now going to correct myself. I misread the original code thinking that the loop variable was being used to select the strings that were being replaced. However, the loopvariable is being used to select the strings that are being modified.

      Yes! These are different. and I'll expand further, but wanted to get this correction out quickly.

      -- Eric Hammond

      Indeed it doesn't!

      I am at a loss to see how I should define the array so that it behaves in the same way as the initial example in the loop. If you could show me this I would be very grateful.

      Phil

Re: array confusion
by hotshot (Prior) on Aug 21, 2003 at 05:40 UTC
    Maybe it's because you forgot the closing curly brackets? (or that's a cope-paste error).

    Hotshot
      Nah, sorry, that's copy'n'paste.

      In the first instance the changes are made to the scalar contents, but in the second the changes are ignored. Why is this?

      Cheers,
      Phil

Re: array confusion
by esh (Pilgrim) on Aug 21, 2003 at 06:13 UTC

    Can you provide a complete but minimal test case which does not do what you expect or want it to?

    -- Eric Hammond