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

I had @range_columns being used in a for loop, which itself was in a while.
My problem was that after the first iteration of the while loop, the contents of @range_columns had been changed, and I didn't know why.

I fixed it by changing for (@range_columns) {
to for my $column_name (@range_columns) {
in the following snippet.
and then doing my $column_num = $dbs->column_num(@table_num, $column_name);
instead of
my $column_num = $dbs->column_num(@table_num, $_);

But I don't know why I had to make that change, because as far as I can tell, $_ wasn't being changed anyway.

The only time I used $_ was to pass as an argument to the method $dbs->column_name, and this method doesn't change the value that is passed to it.
$_ is not passed as a reference, so even if the method did change the value of @_, the original @range_columns that is used in the for loop shouldn't be changed. Is that correct ?
if ($var{tablename}) { for (@range_columns) { my $value = shift @db_row; my $column_num = $dbs->column_num(@table_num, $_); $value = $dbs->format_column_range(@table_num, $column_num, $value +); $value =~ s/ / /g; push @product_cells, $value; } }
Following is a very stripped down copy of the code from the script, the module it calls, and the conf files which the module calls.
There's not enough to run it, just enough to see what's happening.

I've fixed the error that was occuring, but I would like to know why it was occuring.
As always, many thanks for reading this far!
I can't find anything in the following snippets that explains why @range_columns was being altered, but there's nothing in the original files that could effect it other than what's copied 'n' pasted below.
__________ script.cgi __________ #!/usr/bin/perl -wT use strict; use lib '/home/www.domain.net/lib'; use DBStruct; use vars qw/ $dbs %param %var @range_columns @table_num /; $dbs = new DBStruct; $var{tablename} = 'monitors'; @range_columns = qw/ size visible_size recommended_resolution dot_pitch /; @table_num = qw/ 04 01 01 /; while (my @db_row = $sth->fetchrow_array) { my @product_cells; if ($var{tablename}) { for (@range_columns) { my $value = shift @db_row; my $column_num = $dbs->column_num(@table_num, $_); $value = $dbs->format_column_range(@table_num, $column_num, $val +ue); $value =~ s/ / /g; push @product_cells, $value; } } } ___________ DBStruct.pm ___________ package DBStruct; use strict; use Exporter; use vars qw / $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS /; use vars qw / $dbstruct $dbtables /; $VERSION = 1.00; @ISA = qw/ Exporter /; @EXPORT_OK = qw / new column_num /; BEGIN { unless ($dbtables = do('/home/www.domain.net/conf/DBTables.pl')) { die("Could not 'do' /home/www.domain.net/conf/DBTables.pl"); } } sub new { my $self = {}; bless($self); return $self } =head4 column_num() This requires a list of dep numbers (1, 2, or 3) and a column name, and returns the column number. $column = $dbs->column_num($dep_1, $dep_2, $dep_3, $name); or $column = $dbs->column_num(@deps, $name); =cut sub column_num { my $self; if (ref $_[0]) { $self = shift; } my $name = pop; my @dep = @_; my ($dep_1, $dep_2, $dep_3); $dep_1 = shift @dep; if ($dep_2 = shift @dep) { if ($dep_3 = shift @dep) { ### there's 3 dep for ( keys %{$dbtables->{$dep_1}->{dep_2}->{$dep_2}->{dep_3}->{$ +dep_3}->{columns}} ) { if ( $name eq $dbtables->{$dep_1}->{dep_2}->{$dep_2}->{dep_3}- +>{$dep_3}->{columns}->{$_}->{name} ) { return $_ } } return undef } else { ### there's only 2 dep for ( keys %{$dbtables->{$dep_1}->{dep_2}->{$dep_2}->{columns}} +) { if ( $name eq $dbtables->{$dep_1}->{dep_2}->{$dep_2}->{columns +}->{$_}->{name} ) { return $_ } } return undef } } else { ### there's only 1 dep for ( keys %{$dbtables->{$dep_1}->{columns}} ) { if ( $name eq $dbtables->{$dep_1}->{columns}->{$_}->{name} ) { return $_ } } return undef } } =back =cut 1; ___________ DBTables.pl ___________ { '04' => { dep_2 => { '01' => { table => 'monitors', columns => { '01' => { name => 'pr +od_code', }, '02' => { name => 'si +ze', }, '03' => { name => 'vi +sible_size',, }, '04' => { name => 're +commended_resolution', }, '05' => { name => 'do +t_pitch', }, }, }, }, }, },

Replies are listed 'Best First'.
Re: 'for' array being altered - scope
by Sidhekin (Priest) on Sep 26, 2002 at 10:36 UTC

    While I don't see where $_ is changed, keep in mind that it is global. It could be format_column_range or any other code invoked from this that changes it -- even though $_ is not explicitly passed to it.

    I once accidently clobbered $_ because I had a method that, upon first execution, would read a config file. while(<FILE>){} does not look like it would clobber a global, does it? Took me longer than I liked to spot it ...

    The Sidhekin
    print "Just another Perl ${\(trickster and hacker)},"

      Two things here: Sidhekin's problem can be solved by localizing $_ to the current sub instead of just using it. As for the original problem, when you iterate over an array with for() the $_ variable is aliased to the right spot in @array. It *is* your array entry so if you assign to $_ then you are assigning to that spot in your array. It allows for clever things like avoiding an assign operation where map{} would require you to assign the value back into place. This is a micro optimization but it' still a good method to know of.
      @mg = qw(Sam sat on the ground and put his head in his hands. 'I wish + I had never come here, and I don't want to see no more magic,' he sa +id, and fell silent.); ## standard way # create a new array and assign back into place @mg = map ucfirst, @mg; # OR... ## work in place # alter existing array $_ = ucfirst() for @mg;

      Update And this reply misses the entire point of the original question. Oopsie!

        That's all very correct1 and appreciated, but it tells neither me nor fireartist anything we did not already know.

        See, in my case localizing $_ had to be done in the sub that assigned to it (local does dynamic scoping, not lexical), and my problem was finding which sub did such a nasty thing.

        find -name '*.pm' | xargs grep '$_ =' did nothing to help me ...

        fireartist's problem is similar. He knows that assigning to $_ has this effect. He just does not see where this assignment takes place.

        1 <nitpick>... except that you are not so much avoiding assign operations as avoiding building a temporary array. The map version performs one assignment (to an array) total, while the for version performs one assignment (to a scalar) each time through the loop. Still, it is an optimization.</nitpick>

        The Sidhekin
        print "Just another Perl ${\(trickster and hacker)},"