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

The program

perl -le 'for my $x (1) {$x = pack "I", $x}'

produces the warning "Modification of a read-only value attempted at -e line 1". But why? Where is the read-only value?

For contrast, perl -le 'for my $x (0..1) {$x = pack "I", $x}' doesn't warn.

Replies are listed 'Best First'.
Re: Modification of a read-only value attempted?!?
by Bod (Parson) on Jan 07, 2024 at 20:49 UTC

    In the case of for my $x (1) { }, the variable $x is a reference to the value in brackets which is constant.

    Consider this: perl -le "my $y = 2; for my $x ($y) { $x = 1; print $y }" When we change $x, we change the underlying $y. So the output is 1

      Yes, yes, the loop variable in for(each) aliases elements of the list.

      But why is the loop variable read-only if there is only a single numeric (or string) literal in the list?

      AFAICT the program works as expected in all other cases.
      perl -MDevel::Peek -le 'for my $x ([1]->[0]) {Dump $x; $x = pack "I", +$x; Dump $x}' # OK perl -MDevel::Peek -le 'my $y=1; for my $x ($y) {Dump $x; $x = pack "I +", $x; Dump $x}' # OK perl -MDevel::Peek -le 'sub one(){1} for my $x (one) {Dump $x; $x = pa +ck "I", $x; Dump $x}' # OK perl -MDevel::Peek -le 'for my $x (0+1) {Dump $x; $x = pack "I", $x; D +ump $x}' # OK!!! perl -MDevel::Peek -le 'for my $x ("1"."1") {Dump $x; $x = pack "I", $ +x; Dump $x}' # OK perl -MDevel::Peek -le 'for my $x ("1") {Dump $x; $x = pack "I", $x; D +ump $x}' # Error, read-only perl -MDevel::Peek -le 'for my $x (1) {Dump $x; $x = pack "I", $x; Dum +p $x}' # Error, read-only

      Experimenting on perlbanjo.com, it seems that this behavior goes back to at least Perl 5.8.

        The difference is that in for my $x (1) {...} the 1 is a literal constant. (0..1) on the other hand is an expression evaluating to the list (0,1). The loop is aliasing to the anonymous elements of the list, not to the literal boundaries in (0..1).

        Perhaps it is helpful to consider that for my $x (0,1) {...} also dies with Modification of a read-only value attempted at -e line 1.

        • 0+1 is an expression: ok!
        • "1" is a literal constant: not ok!
        • (0..1) is an expression: ok!
        • (0,1) is a literal list: not ok!

        While 0 and 1 evaluate to a constant, 0..1 evaluates to two scalars which aren't constants.

        That said, for (0..1) doesn't actually use the range operator as an optimization. But the result is the same.

Re: Modification of a read-only value attempted?!?
by ikegami (Patriarch) on Jan 09, 2024 at 19:45 UTC

    Solutions:

    perl -e'for ( 1 ) { my $x = pack "I", $_; }'
    perl -e'for my $x ( 1 ) { my $y = pack "I", $x; }'
    perl -e'for my $x ( 1 ) { my $x = pack "I", $x; }'
    perl -e'for my $x ( my $tmp = 1 ) { $x = pack "I", $x; }'

    Explanation:

    1 returns a read-only scalar. It's read-only because a given 1 literal always returns the same scalar.

    Foreach aliases the variable, so $x is an alias of that read-only scalar.

    To understand what that means, it might be clearer if we take the foreach out of the equation.

    use feature qw( say ); use experimental qw( refaliasing declared_refs ); my $y = 1; # Make `$y` have the same value as `1`. say $y; # 1 $y = 2; # Make `$y` have the same value as `2`. say $y; # 2 my \$x = \1; # Make `$x` an alias of `1`. say $x; # 1 $x = 2; # Modification of a read-only value attempted say $x;

    Just like in your code, the last assignment in this program dies because it attempts to modify $x and thus the scalar to which 1 evaluates. It would be bad if 1 started to evaluate to a scalar with a value other than one.