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

Greetings fellow monks,

Apologies if either of these have been answered elsewhere and I just hadn't found them ...

First, according to the documentation for the range operator "..":

In list context, it returns a list of values counting (up by ones) from the left value to the right value. If the left value is greater than the right value then it returns the empty list.

So how in the world is the code:

#!/usr/bin/perl -w use strict; use warnings; my @range = ('e' .. 'd'); printf "%s\n", join(" ", @range);

Resulting in this:

e f g h i j k l m n o p q r s t u v w x y z

?

Second, with this code:

#!/usr/bin/perl -w use strict; use warnings; sub letters { foreach my $letter ('a' .. 'c', 'd' .. 'f') { print " $letter"; $letter = uc $letter; } print "\n"; } letters(); letters(); letters();

the results are truly not what I expect(ed):

a b c d e f A B C D E F A B C D E F

The first time through, the letters are printed in lower-case, but subsequent passes indicate the line:

$letter = uc $letter;
has somehow modified the list.  Can that really be what has happened?

It doesn't seem to have the same effect as:

sub letters { foreach my $letter ('a' .. 'f') { print " $letter"; $letter = uc $letter; } print "\n"; } # Prints... a b c d e f a b c d e f a b c d e f

which behaves as I would have thought.

Nor does constructing the array first have the apparent aberrant outcome:

sub letters { my @letters = ('a' .. 'c', 'd' .. 'f'); foreach my $letter (@letters) { print " $letter"; $letter = uc $letter; } print "\n"; } # Prints... a b c d e f a b c d e f a b c d e f

The results were the same both with Perl 5.8.5 on RedHat EL4.3, and Perl 5.10.0 on Windows XP.

Can anyone enlighten me?


s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

Replies are listed 'Best First'.
Re: Two Range Operator Anomalies
by hbm (Hermit) on Mar 11, 2009 at 16:12 UTC

    For the first question, perlop goes on to say:

    If the final value specified is not in the sequence that the magical increment would produce, the sequence goes until the next value would be longer than the final value specified.

    I read that to mean, 'e' .. 'd' runs through 'z' and stops at 'za' 'aa' (thanks ikegami).

    Update: Correction!

      and stops at 'za'

      Stops at 'aa', actually

      $ perl -le'print ++($_="z")' aa $ perl -le'$,=" "; print "e".."d"' e f g h i j k l m n o p q r s t u v w x y z $ perl -le'$,=" "; print "e".."za"' e f g h i j k l m n o p q r s t u v w x y z aa ab ...[647]... yz za

      perlop goes on to say:

      But doesn't that feel like it's documenting a bug?

Re: Two Range Operator Anomalies
by repellent (Priest) on Mar 11, 2009 at 16:38 UTC
    Your second "anomaly" has to do with foreach loop aliasing. Particularly:

      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.

    So:
    my @a = (1,2,3,4); for my $i (@a) { $i = 5 }; print "@a"; # prints: 5 5 5 5

    while:
    for my $i (1,2,3,4) { $i = 5 };

    errors out with: Modification of a read-only value attempted ...

    I think what's happening with your 3 examples of the second anomaly is this:

    1. foreach my $letter ('a' .. 'c', 'd' .. 'f') creates a list of lvalues due to the comma operator in the middle. Perhaps this is done only once at compile time? which explains the persistence.
    2. foreach my $letter ('a' .. 'f') has been optimized as a counting loop, so the range operator does NOT generate a list.
    3. foreach my $letter (@letters) always has @letters redefined right before the loop, so each call to letters() yields consistent results.

      Perhaps this is done only once at compile time?

      Yes. In the past, I have confirmed this by generating long lists and taking memory measurements. It can also be seen in the opcode tree

      $ perl -MO=Concise -e'@a = (1..3)' ... - <1> ex-list lK ->6 3 <0> pushmark s ->4 5 <1> rv2av lKP/1 ->6 4 <$> const[AV ] s ->5 ...

      compared with

      $ perl -MO=Concise -e'@a = ($x..$y)' ... - <1> ex-list lK ->7 3 <0> pushmark s ->4 - <1> null lKP/1 ->- 6 <1> flop lK ->7 d <1> flip[t6] lK ->7 4 <|> range(other->5)[t5] lK/1 ->c - <1> ex-rv2sv sK/1 ->d c <#> gvsv[*x] s ->d - <1> ex-rv2sv sK/1 ->6 5 <#> gvsv[*y] s ->6 ...

      so the range operator does NOT generate a list

      In fact, there is no range operator.

      $ perl -MO=Concise -e'for ($x..$y) {}' ... 7 <{> enteriter(next->9 last->c redo->8) lKS ->a - <0> ex-pushmark s ->3 - <1> ex-list lK ->6 3 <0> pushmark s ->4 - <1> ex-rv2sv sK/1 ->5 4 <#> gvsv[*x] s ->5 - <1> ex-rv2sv sK/1 ->- 5 <#> gvsv[*y] s ->6 6 <#> gv[*_] s ->7 ...

      compared with

      $ perl -MO=Concise -e'for ((),$x..$y) {}' ... 8 <{> enteriter(next->a last->d redo->9) lK ->b - <0> ex-pushmark s ->3 - <1> ex-list lKM ->7 3 <0> pushmark sM ->4 - <0> stub lPM ->4 - <1> null lKM/1 ->- 6 <1> flop lKM ->7 g <1> flip[t4] lK ->7 4 <|> range(other->5)[t3] lK/1 ->f - <1> ex-rv2sv sK/1 ->g f <#> gvsv[*x] s ->g - <1> ex-rv2sv sK/1 ->6 5 <#> gvsv[*y] s ->6 7 <#> gv[*_] s ->8 ...
Re: Two Range Operator Anomalies
by ikegami (Patriarch) on Mar 11, 2009 at 16:05 UTC

    For the first question, read further. "The range operator (in list context) makes use of the magical auto-increment algorithm if the operands are strings."

    For the second question, you got bitten by the side-effect of an optimisation. Constant ranges are expanded into a list at compile-time. You modified this list, affecting future use of it. Workaround:

    for ('a'..'f') { my $letter = $_; # Make modifiable copy ... }

    Update: Misunderstood the first question

      Thanks, ikegami.

      The documentation "The range operator (in list context) makes use of the magical auto-increment algorithm if the operands are strings." didn't quite shed any light on it, as I'm already familiar with the magical auto-increment feature.  (But I think hbm got it below, which clarified something that the documenation could possibly use better wording for).

      As to the second, however, you say the workaround is to use a "modifiable copy":

      for ('a'..'f') { my $letter = $_; # Make modifiable copy ... }

      But why isn't the my that I'm using here:

      foreach my $letter ('a' ... 'c', 'd' ... 'f') { print " $letter"; $letter = uc $letter; }

      doing exactly that??

      Update:  Actually, I see why. Of course, because it's assigning to a modifiable lvalue of the variable. Never mind.

      But I'm still curious why this:

      #!/usr/bin/perl -w use strict; use warnings; sub letters { foreach my $letter ('a' ... 'c') { print " $letter"; $letter = uc $letter; } print "\n"; } letters(); letters(); letters();

      Doesn't exhibit the same behavior? Is it simply that it doesn't undergo the same optimization step you referred to?


      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

        The documentation [...] didn't quite shed any light on it,

        I thought misunderstood the question and have already struck out that part of my post while you were composing your reply.

        But why isn't the my that I'm using here doing exactly that??

        For loop iterators are aliases for efficiency and to allow you to modify the list.

        for my $s (@strings) { $s = trim($s); }

        But I'm still curious why [foreach my $letter ('a' ... 'c')] Doesn't exhibit the same behavior?

        There's no range operator in that statement. for (X .. Y) is a counting loop. It doesn't flatten X .. Y. See Re: For vs. Foreach