Unexpected alteration of array's content

P

Peter Hill

The following program gives an output which I didn't expect with Perl 5.8
#! /usr/bin/perl -w
use strict;
my @lines = ('one','two','three');
for my $line (@lines){
$line =~ s/w/x/g;
}
print "@lines\n";

#output is "one txo three"


Am I wrong in expecting the array not to be modified when I'm only modifying
a local variable derived from it? Apologies if this is a commonly known
effect, I've just returned to Perl programming.
tia
 
S

Sam Holden

The following program gives an output which I didn't expect with Perl 5.8
#! /usr/bin/perl -w
use strict;
my @lines = ('one','two','three');
for my $line (@lines){
$line =~ s/w/x/g;
}
print "@lines\n";

#output is "one txo three"


Am I wrong in expecting the array not to be modified when I'm only modifying
a local variable derived from it? Apologies if this is a commonly known
effect, I've just returned to Perl programming.

Your expectation is incorrect. The behaviour is documented in:

perldoc perlsyn

Reading the documentation is usually a better way of determining what
a statement will do then guessing.
 
J

Jürgen Exner

Peter said:
The following program gives an output which I didn't expect with Perl
5.8 #! /usr/bin/perl -w
use strict;
my @lines = ('one','two','three');
for my $line (@lines){
$line =~ s/w/x/g;
}
print "@lines\n";

#output is "one txo three"


Am I wrong in expecting the array not to be modified when I'm only
modifying a local variable derived from it? Apologies if this is a
commonly known effect, I've just returned to Perl programming.

From "perldoc perlsyn":
Foreach Loops
[...] If any element of LIST is an lvalue, you can modify it by
modifying VAR
inside the loop.

jue
 
M

Matija Papec

for my $line (@lines){
$line =~ s/w/x/g;
}
print "@lines\n";

#output is "one txo three"


Am I wrong in expecting the array not to be modified when I'm only modifying
a local variable derived from it? Apologies if this is a commonly known
effect, I've just returned to Perl programming.

It is a documented feature, to avoid it try,
for my $line (map $_, @lines){
 
M

Mina Naguib

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Peter said:
The following program gives an output which I didn't expect with Perl 5.8
#! /usr/bin/perl -w
use strict;
my @lines = ('one','two','three');
for my $line (@lines){
$line =~ s/w/x/g;
}
print "@lines\n";

#output is "one txo three"


Am I wrong in expecting the array not to be modified when I'm only modifying
a local variable derived from it? Apologies if this is a commonly known
effect, I've just returned to Perl programming.
tia

From perldoc perlsyn, "Foreach Loops" section:

"If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop.
Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail.
In other words, the foreach loop index variable is an implicit alias for each item in the list that
you're looping over."


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQE/fCzHeS99pGMif6wRArTwAJ9RYoxngFkl7oVcTMsH4zlNm1YE6gCeMvNK
5hyeajRWju7geYhzy/0B4lU=
=PPqE
-----END PGP SIGNATURE-----
 
B

Brian McCauley

Matija Papec said:
It is a documented feature, to avoid it try,
for my $line (map $_, @lines){

Personally I'm supprised that works. But even though it does (on at
least one version of Perl I've tried) I still prefer:

for my $line (@{[@lines]}){


--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
M

Matija Papec

X-Ftn-To: Brian McCauley

Brian McCauley said:
Personally I'm supprised that works.

? :)
But even though it does (on at
least one version of Perl I've tried) I still prefer:

for my $line (@{[@lines]}){

Do you know if there is some significant speed difference?
I've started using double maps since single map start to corrupt hash
slices(very frustrating). IMO, there is absolutely none or close to none
usefulness of aliasing $_ for grep and map.
 
R

Roy Johnson

Is it correct to call the loop variable an "alias" for $_? I don't
think so. If a variable is supplied, it is used; if no variable is
supplied, $_ is used. References to the loop variable, if it is not
$_, do not affect $_ (nor do operations on $_ affect the loop
variable).

Perl's making loop variables into transparent references to loop
values is, perhaps, inconsistent with the normal understanding of
assigning a value to a variable, but perl is admittedly inconsistent,
when inconsistency is desired.

For reasons of efficiency (if it is an array of complex structures,
slinging them around is costly) and convenience (often, we do want to
modify all the elements of an array), the behavior described is
desirable.

The straightforward way to get the "other" behavior is to declare a
variable within the loop:

for my $k (@list) {
my $copy = $k;
$copy =~ s/w/x/g;
...
}

I can't see a strong reason for going with the tortured alternatives
of transforming @list into a non-lvalue.
 
T

Tad McClellan

Roy Johnson said:
Is it correct to call the loop variable an "alias" for $_?


No.

perlsyn says:

the foreach loop index variable is an implicit alias
for each item in the list that you're looping over.

If a variable is supplied, it is used; if no variable is
supplied, $_ is used.


And perlsyn is correct in either case.

The "aliasing" does not depend on what you choose to use for
the loop control variable.
 
R

Roy Johnson

What's surprising about it? map() generates a new array, which is used
in the loop. It would be equivalent to say

for my $line (my @copy = @array) {

except, of course, that this names the copy. If you want to avoid new
names, you can say

for my $line (my @array = @array) {

which might cause people to blink. I can tell you like code that makes
people blink. :)
But even though it does [...] I still prefer:

for my $line (@{[@lines]}){

This also generates a new copy of the entire array (into an anonymous
ref, which is then dereferenced). It has a somewhat higher blink
factor than the earlier examples.

But why not just copy the element you're playing with? Save a little
memory.

for (@lines) {
my $line = $_;
....

Or re-define the loop variable inside the loop. Then you've protected
the loop variable from possible modification, and you haven't
cluttered your namespace. This is actually rather slick, and after a
blink, it's actually apparent what you're doing: working on a local
copy.

for my $line (@array) {
my $line = $line;
$line++;
print "Affected $line\n";
}
print join('', @array), "\n";

If you want to use $_, you'll need to make it local, rather than my:

for (@array) {
local $_ = $_;
$_++;
print "Affected $_\n";
}
print join('', @array), "\n";
 
B

Brian McCauley

What's surprising about it? map() generates a new array,

No, map returns a _list_ not an _array_. It is quite possible for a
function that returns a list to return a list of lvalues.

If I do:

for my $line (grep 1, @lines){

Then @lines _is_ still modified.

The reason it works with map but not grep is that the expression in
map is evaluated as an rvalue rather than an lvalue. This is
surprising since expressions in Perl are usually only converted to
rvalues at the last possible moment.
It would be equivalent to say

for my $line (my @copy = @array) {

except, of course, that this names the copy. If you want to avoid new
names, you can say

No, that creates an array not just a list.
for my $line (my @array = @array) {
which might cause people to blink.

Yes, indeed. Much better to use an anonymous array.
I can tell you like code that makes people blink. :)

No I don't.
But even though it does [...] I still prefer:

for my $line (@{[@lines]}){

This also generates a new copy of the entire array (into an anonymous
ref, which is then dereferenced).

Yes - I know what it does.
It has a somewhat higher blink factor than the earlier examples.

Not once you get used to it (I certainly blinked at the map example).
But why not just copy the element you're playing with? Save a little
memory.

for (@lines) {
my $line = $_;

Yes I was going to post exactly this in answer to the OP and had even
got as far as typing a follow-up but then decided it was too obvious
to mention and I didn't hit send.
If you want to use $_, you'll need to make it local, rather than my:

for (@array) {
local $_ = $_;
$_++;
print "Affected $_\n";
}
print join('', @array), "\n";

By rights that should modify each of the elements of @array in turn
but put them back afterwards. That's not what it does in current
versions of Perl but maybe one day it will.

If @array is tied really odd (bad) things happen. Again maybe this
will be fixed one day. If it is fixed then you can probably expect
the behavior for untied @array to change too.

Please see other current thread about why local $_ can do strange
things.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
C

Carlton Brown

Purl Gurl said:
Rather oxymoronic of you boys to claim a loop variable should not
be called an alias, then cite Perl documentaion which refers to
a loop variable as an "alias."

Um... nobody said a loop variable shouldn't be called an alias. It
truly is an alias, just like $_ is also an alias. Each one is an
alias to the next element of the list. What is incorrect is when
somebody says that a loop variable is an alias to $_. That's because
perl only defines $_ if no loop variable was defined... thus, the two
never co-exist in the same scope, thus they could not possibly be
aliases to each other. Here's a demonstration:

#!/usr/bin/perl
@lights = qw(red green yellow);
print "Show me the loop var:\n";
foreach $color (@lights) {
if (defined($_)) { print "Got $_ by referencing \$_\n" }
if (defined($color)) { print "Got $color by referencing
\$color\n" }
}

print "\nShow me \$_:\n";
foreach (@lights) {
if (defined($_)) { print "Got $_ by referencing \$_\n" }
if (defined($color)) { print "Got $color by referencing
\$color\n" }
}
Printed results:

Show me the loop var:
Got red by referencing $color
Got green by referencing $color
Got yellow by referencing $color

Show me $_:
Got red by referencing $_
Got green by referencing $_
Got yellow by referencing $_

That also explains the nesting problem you posted earlier... when you
defined a line variable, $_ was therefore not defined, therefore your
split created an array of nulls which wouldn't have printed anything
in the outer loop. A print statement in your outer loop would have
demonstrated this behavior.
 
C

Charles DeRykus

...

By rights that should modify each of the elements of @array in turn
but put them back afterwards. That's not what it does in current
versions of Perl but maybe one day it will.

Hm, I'm not sure I understand "modify each of the elements of @array in
turn but put them back afterwards" ...?

Are you suggesting that, even if $_ is localized, any changes
will still affect @array within that scope..? (I thought that
localization was intended to prevent such modification altogether...)

Or were you saying something entirely different?
 
C

Charles DeRykus

Hm, I'm not sure I understand "modify each of the elements of @array in
turn but put them back afterwards" ...?

Are you suggesting that, even if $_ is localized, any changes
will still affect @array within that scope..? (I thought that
localization was intended to prevent such modification altogether...)

Or were you saying something entirely different?

Sorry, to follow my own post but I think now I see the logic.
Since $_ is an alias, any alternations within that scope should
affect the original array (although that's not the current practice)...
 
R

Roy Johnson

Purl Gurl said:
Two of you boys clearly indicated I am wrong for using a
very general term "alias" in my article,

Nope. Read better. While I highlighted the word "alias", I did not
simply say you were wrong for calling it an alias, but for calling it
an alias to $_.
Both used this term I used for no reason
other than to assist understanding,

You demonstrated no understanding, and communicated nothing useful or
coherent.

You have made it abundantly clear through your personal attacks,
sexist baiting, and inflammatory language that your primary interest
is in exerting some dominance over this group.

Get used to the fact that most of the world is not nearly as impressed
with you as you are.

A loop variable is not an alias to $_, it is an alias to the current
element of the loop. If the variable is $_, $_ is an alias; if the
variable is not $_, then $_ is not part of the picture at all. It is
certainly not aliased to.
 
R

Roy Johnson

Purl Gurl said:
Roy Johnson wrote:

(snipped)


What politically correct term would you suggest for us to use
so as not to upset Perl 5 Cargo Cultists?

It's not about political correctness. It's about accuracy. Words mean
things. And not always just what you want them to mean, Humpty Dumpty.
Ok, so we should call a loop variable a "reference" according
to the Book of Perl.

In this case, a coder should be able to dereference this variable,
and pull out deferenced values.

Words mean things. I put "transparent" in there for a reason.
Rather oxymoronic of you boys to claim a loop variable should not
be called an alias, then cite Perl documentaion which refers to
a loop variable as an "alias."

It's not oxymoronic. Words mean things. Nor did any of "us boys" claim
a loop variable should not be called an alias. When you just pick and
choose words out of a sentence, reconstruct a new one, and pretend
that it was the original position, it's not very honest.
Very good. Your word "inconsistent" is a politically correct
term for "illogical."

I refer you to Larry Wall for more about inconsistency:

Psychotics are consistently inconsistent. The essence of sanity is
to be inconsistently inconsistent.
My preference is to discuss Perl related issues rather than
conceal, excuse and argue about the shape of a table.

You haven't made that apparent. Your apparent preference is to attack
everyone who questions your postings in any way.
 
R

Roy Johnson

Brian McCauley said:
No, map returns a _list_ not an _array_. It is quite possible for a
function that returns a list to return a list of lvalues.

Alrighty, then. I don't see the importance of the distinction here,
but I'm quite certain you are correct.
If I do:

for my $line (grep 1, @lines){

Then @lines _is_ still modified.

This was new to me.
No I don't.

Then I'm sorry to inform you that the idiom you prefer is still going
to cause a WTF moment for the casual reader. At least with a simple
assignment, it is intuitively obvious that something is being copied.
Not once you get used to it (I certainly blinked at the map example).

There is no reason for @{[@ar]} to become a familiar idiom. It doesn't
do anything that is often going to be necessary. It's not difficult to
decipher, but it still takes a blink.
Yes I was going to post exactly this in answer to the OP and had even
got as far as typing a follow-up but then decided it was too obvious
to mention and I didn't hit send.

Unless there is a reason given that the obvious solution shouldn't be
used, it's generally a good idea to mention it. People will overlook
the darndest things, sometimes.
Please see other current thread about why local $_ can do strange
things.

I will do that, if I can find it.
 
D

David H. Adler

Alrighty, then. I don't see the importance of the distinction here,
but I'm quite certain you are correct.

Perhaps a look at "What is the difference between a list and an array?"
in perlfaq4 might be in order then... unless you just mean you don't
see the importance in this *specific* case.

dha
 
C

Carlton Brown

Purl Gurl said:
My $element is a precise copy of the $_ variable.

Call $element any f'n thing you want. It is the $_ variable.

That is simply inaccurate. $element points to a piece of data. $_
points to the same piece of data. Just because they point to the same
thing doesn't mean that they point to each other. That is saying that
2 signs pointing to the same street actually point to each other.
They don't, although following them will get you to the same street.
Point I made earlier is, as an alias, as a moniker,
as a new name, as a copy, $element should not change
the original contents of the array.

I see the source of the confusion. An alias and a copy are not the
same things. A copy would mean 2 completely separate pieces of data.
In Perl 4, iterating loops would do this. But as pointed out, in Perl
5, the loop variable isn't a copy of $_, it is an alias. Not only
that, it is an alias to the last block in which it was used. If you
get a new block, you get a new $_.

Consider this, in which $_ is changed by the introduction of a map
statement, without the use of nesting:

#!/usr/bin/perl
@words=qw(foo bar bin bat);
@nums=qw(1 2 3);
foreach (@words) {
print "I am on iteration $_\n";
@otherwords = map { reverse } words;
print "Default variable is now $_\n";
}

Pick a version of perl that suits you, understand it, love it. But at
least understand why you love it.

(snip)
You have stated $_ is undefined when an alias is used.
This is untrue. It is defined using a different name;
perl core $_ becomes whatever variable name is assigned.

I would challenge you to find a case in which you name a loop variable
but you're still able to use $_ without first invoking one of the
functions that vivifies it (such as another foreach, map, grep, glob
statement). It's not possible because that isn't how perl works.

(snip)
What is the logic of allowing $_ to be renamed but
not change any functions? There is no logic to this.

Only benefit is keeping visual track of alias names
in very complex nested loops.

Well, that and not violating the principle of variable scoping, which
would be *highly* illogical by any measure. If you want to throw out
scoping, I suggest you try shell or VB for a while and experience the
true joy of debugging a scope-less language.
 
C

Carlton Brown

Purl Gurl said:
Not localizing $element is illogical and leads to
confusion until a person realizes it is, de facto,
the perl core default $_ variable.

This is an odd statement, because they ARE localized.

perlsyn sayeth:
The foreach loop iterates over a normal list value and sets the
variable VAR to be each element of the list in turn. The
variable is implicitly local to the loop and regains its former
value upon exiting the loop.

Local variables perpetuate to inner scopes, but their outer values are
restored when the inner scope is exited. In fact, that's the
definition of local. However, I will fully agree that this is one of
those "hidden features" that doesn't make sense until you are aware
that it exists, and why it was done that way. Especially considering
that most "best practices" discourage the use of local(), and yet here
we have it built into the language.

Why was it done that way? I think the reasons have been pretty well
illustrated by you, me, and everyone else. If you won't buy my
explanation, maybe you'll buy Larry Wall's, who so quoth a few years
back:

"...you'd have subroutines tromping each other's $_ variables all over
the place if they didn't clean up after themselves....(snip)
This is just another one of those cases where you have to design a
language in its own context, and not be too beholden to how other
languages do it. Your language can't be totally intuitive to everyone
all the time, because everyone is coming from a slightly different
linguistic background. Whether you spell it omelet or omelette, you
still have break eggs to make it."

http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&[email protected]

The problem actually comes from the existence of $_ itself, which you
could also consider dubious. If you didn't create the variable, then
how would you intuitively know whether it's scoped globally,
lexically, or dynamically? And the answer is, you can't know unless
you have educated yourself on what it means in each context -
admittedly not a transparent task. If you don't want to do that, you
don't have to use $_. In fact, for exactly this reason, I usually
avoid using $_ except when the context is unmistakable.

But all these things being true, $_ is still not an alias for a
defined loop variable, the two never co-exist in the same scopes, and
their behavior is predictable if you understand that they are scoped
as though they had been declared using local().
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top