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

Dear brothers in this Monastery

It has been brought to my attention this behavior:

% perl -MData::Dumper $c->[3] = 3; print Dumper( $c ); for ( @$c ) { $_++ } print Dumper( $c ); __END__ $VAR1 = [ undef, undef, undef, 3 ]; $VAR1 = [ 1, 1, 1, 4 ];

However, this code, crashes:

% perl -MData::Dumper $c->[3] = 3; print Dumper( $c ); map { $_++ } @$c; print Dumper( $c ); __END__ $VAR1 = [ undef, undef, undef, 3 ]; Modification of a read-only value attempted at - line 3.

Changing the above code for: map { $_++ if defined $_ } @$c yields the correct result of 3 undefs and 4 (with 4 changes, because map processed 4 elements nevertheless). And we get the same result if we test the definedness of $_ in the for loop. This happens as well working directly with the array (instead of a reference) and as well inside a hash, etc...

I interpret this is happening: for (in the lack of a lexical or otherwise explicit variable before the array comes) aliases $_ for the local BLOCK with each value of the array, as they come. On the other hand, map expects an EXPR or a BLOCK to act, aliasing $_ inside the BLOCK or EXPR to each member of the array, one at a time as they come from the array.

Now, in the first case exposed (for), for some reason, the undefs in the array are first converted to 0, then we increment them. In the case of map, we are trying to increment an undef, which is not possible.

'diagnostics' is not very helpful in this case, and the examples offered as a possible source of the error actually talk about a for loop, not a map.

Of course, if Perl had real sparce arrays (as we are likely to get for Perl 6) this would probably be a different can of worms, as we would be still facing the problem of what to do with those array elements not yet initialized.

I ask to the monks of this noble Monastery: is this the expected behavior of map and for in this case, or are we facing an ugly bug?

Most grateful,

--
our $Perl6 is Fantastic;

Replies are listed 'Best First'.
Re: "Sparse" Array behavior with "for" and "map"
by broquaint (Abbot) on Jul 22, 2003 at 14:42 UTC
    Paraphrased from the CB
    The short answer to your question is that the way for and map alias $_ isn't orthogonal (map does a straight alias, for does magic). This behaviour is known about, so not really a bug. I think it's intentional DWIMery as it's idiomatic to modify the current topic ($_ in this case) in for whereas it's very bad form to modify the topic in map. Perhaps it's worth noting in the map docs this effect of aliasing an empty $_ makes it READONLY, but I somehow doubt it would be applied ala doctor, it hurts when I do this ....

    HTH

    _________
    broquaint

Re: "Sparse" Array behavior with "for" and "map"
by roju (Friar) on Jul 22, 2003 at 14:42 UTC
    First off, "we are trying to increment an undef, which is not possible". This is not the case, for example: perl -e '$_ = undef; print ++$_' does in fact print 1. Undef is treated as 0 in a numerical context. Since the for loop hits the undefs, they get incremented to 1.

    Similarly, for the map, I have:

    $ perl -e '$a = [undef, undef, 3]; map {$_++} @$a; print "@$a"' 1 1 4

    Which is the expected result. If you want undef values to be ignored, put an explicit test. I.E. $_++ if defined $_; Also, map in a void context?

    Update: Well, you're right, if you create your array your way, it is an attempt to modify a non-existant element. I suggest you just not use the map in a void context. Instead of map {$_++} @a; do @b = map {$_+1} @a; which is safer.