This evening, I was pondering constructs I regularly use in Perl, especially ones which tend to get overly complicated as the requirements increase.

One such that continues to frustrate me is when writing something like:

my @array = qw(red blue green yellow black white purple brown orange g +ray); foreach (@array) { do_something_with($_); }

Which is simple enough. But if I suddenly realize that do_something_with() ought to know the index of the item in the array:

# Inputs: $1 ... color name # $2 ... The color index (a new arg) ## sub do_something_with { my ($color, $idx) = @_; # For now, just print the color prefixed with its index... printf " %2d. '$color'\n", $idx; }

... it wastes time, and extra space, to go back and modify the foreach loop to the equivalent for loop (ignoring for a moment the synonymity of foreach and for):

for (my $i = 0; $i < @array; $i++) { my $item = $array[$i]; do_something_with($item, $i); }
Even a shorter idiom takes an extra line, and isn't quite as "clean" (imo), as the $i has to be defined outside of the loop:
my $i; foreach (@array) { do_something_with($item, $i++); }
Then it occurred to me that a state variable (available in Perl 5.10) should work.  Sure enough, the following does exactly as I had hoped:
use feature ":5.10"; my @array = qw(red blue green yellow black white purple brown orange g +ray); foreach (@array) { do_something_with($item, state i++); } # Prints # 0. 'red' # 1. 'blue' # 2. 'green' # 3. 'yellow' # 4. 'black' # 5. 'white' # 6. 'purple' # 7. 'brown' # 8. 'orange' # 9. 'gray'

Even a preincrement is simple and cleanly "contained":

for (@array) { do_something_with($_, ++ state $i); } # Prints # 1. 'red' # 2. 'blue' # 3. 'green' # 4. 'yellow' # 5. 'black' # 6. 'white' # 7. 'purple' # 8. 'brown' # 9. 'orange' # 10. 'gray'

This will surely save me future editing time (as long as I'm on a system with Perl 5.10 installed).

Moreover, the idiom works with map just as nicely:

map { do_something_with($_, state $i++) } @array;

Does anyone else have examples to share, of idioms they've discovered, to decrease the time spent changing functionality in their Perl code?


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

Replies are listed 'Best First'.
Re: Interesting Use for "state" Variables
by Jenda (Abbot) on Mar 10, 2009 at 02:11 UTC

    All you need is another pair of curlies. And it will actually work correctly, unlike the state variables.

    { my $i; foreach (@array) { do_something_with($item, $i++); }}

    BTW, you should NOT use

    printf " %2d. '$color'\n", $idx;
    what if the $color contains some format specifiers?!? Please use
    printf " %2d. '%s'\n", $idx, $color;
    instead.

Re: Interesting Use for "state" Variables
by massa (Hermit) on Mar 10, 2009 at 02:01 UTC
    Except that the next time you execute the same loop, you know $i will not start over at 0, don't you?? :-)
    []s, HTH, Massa (κς,πμ,πλ)
      Yes, you're right. That "fly in the ointment" just now occurred to me, and I was going to update my post to point this out. It unfortunately makes many applications (such as tracking line numbers from a file) useless, except for the very first time.  Rats.

      It brings me back to a previous thought -- I wonder what it would take to implement a special Perl variable to track the loop count? For example, $* is now deprecated; perhaps it could be "reused" for something like this?

      Anyway, thank you for finding my flaw early on.


      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Interesting Use for "state" Variables
by BrowserUk (Patriarch) on Mar 10, 2009 at 02:16 UTC

    The D language has a nice pragmatic variation on the foreach loop.

    Written this way, c takes on each of the values in the array a in turn:

    char[] a; ... foreach (char c; a) { ... }

    Written this way, the same thing happens, but also i takes on the index of the value also:

    char[] a; ... foreach (int i, char c; a)

    I don't think there's any possibility of getting that into 5.12 (nor even Perl 6 at this late stage), but it one of those few features of other languages I miss when I'm writing Perl.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      perl6 already has the kv thingy:
      for @x.kv -> $index, $value { say $index, ' ', $value }
      (tested with rakudo, it works)
      []s, HTH, Massa (κς,πμ,πλ)

      Python has enumerate for this. For example:

      for i, item in enumerate(array): do_something_with(item, i)
      The nearest thing I can find to that in Perl is the List::MoreUtils pairwise function, something like:
      my @idx = 0 .. $#array; pairwise { do_something_with($b, $a) } @idx, @array;
      or perhaps the CPAN Array::Each::Override module.

        You can achieve something similar without modules a couple of ways:

        @a = 'A'..'J'; for my $r ( map { k=> $_, v=> $a[ $_ ] }, 0 .. $#a ) { print "@{ $r }{ k, v }\n" } 0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J

        Or

        @a = 'A'..'J'; for my $r ( map [ $_, $a[ $_ ] ], 0 .. $#a ) { print "$r->[0]:$r->[1]\n" } 0:A 1:B 2:C 3:D 4:E 5:F 6:G 7:H 8:I 9:J

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Interesting Use for "state" Variables
by ELISHEVA (Prior) on Mar 10, 2009 at 07:25 UTC
    When I first learned Perl I used to be annoyed that $x =~ s/.../.../ changed $x. Often I wanted both the original value and the transformed around. I thought I had no choice but this two liner. To my eyes, this is just plain confusing since $transformed isn't transformed (yet):
    my $transformed=$original; $transformed =~ s/.../.../;

    Then I discovered that I could keep the original and the transformed value by doing this one liner:

    (my $transformed=$original) =~ s/.../.../;

    Best, beth

Re: Interesting Use for "state" Variables
by lostjimmy (Chaplain) on Mar 10, 2009 at 12:54 UTC

    ... it wastes time, and extra space, to go back and modify the foreach loop to the equivalent for loop (ignoring for a moment the synonymity of foreach and for):

    for (my $i = 0; $i < @array; $i++) { my $item = $array[$i]; do_something_with($item, $i); }

    Even a shorter idiom takes an extra line, and isn't quite as "clean" (imo), as the $i has to be defined outside of the loop:

    my $i; foreach (@array) { do_something_with($item, $i++); }

    I think the following is just as simple as using state or any other variation, and doesn't require any more lines of code than your original:

    for my $i (0..$#array) { do_something_with($array[$i], $i); }
    ...unless I'm missing something.

Re: Interesting Use for "state" Variables
by Beechbone (Friar) on Mar 10, 2009 at 10:37 UTC
    You can "save" the state if you use it in the declaration, not the execution part:
    use feature ":5.10"; my %hash = map { $_ => state $i++ } qw(red blue green yellow black whi +te purple brown orange gray); for (1..2) { while (($k,$v) = each %hash) { do_something_with($k, $v); } }
    (Defined execution order left as exercise for the reader)

    Search, Ask, Know
      for (1..2) { while (($k,$v) = each %hash) { do_something_with($k, $v); } }
      Talking about state, you know your code may not do what you expect it to do, don't you? 'each' is an iterator which keeps state on the hash itself. Which means that if the loop exits prematurely (which could even happen in this code if do_something_with contains a 'last') the next time around, it will iterate over the entire hash.

      You may want to develop the habit to write keys %hash; in void or scalar context right before each each. (It's not an easy habit to get used to - each isn't common enough for that - half of the time, I forget it my own good advice).

Re: Interesting Use for "state" Variables
by ruzam (Curate) on Mar 10, 2009 at 19:49 UTC
    foreach (@array) { do_something_with($item, state i++); }
    Doesn't look any cleaner than
    my $i; foreach (@array) { do_something_with($item, $i++); }
    (imo)

    And since 'state' is something I rarely use (ok never) you can bet I'll be looking it up in the docs every time I see it to be sure of exactly what it's doing. It's sometimes annoying that there's no magic variable to get an index in a foreach, but I think you've sacrificed readability (and portability) for the sake of a single line of convenience?

      How do you think the other veterans felt about this crazy new my keyword that they had to lookup everytime they encountered it when my was first introduced?! </joke>

      I find your first bit of code cleaner--I always hate taking up an extra line above my foreach loops.

      And you didn't even know bears could type.

Re: Interesting Use for "state" Variables
by zentara (Cardinal) on Mar 10, 2009 at 11:48 UTC
    I'm no structure expert, nor state variable afficienado, but isn't this the sort of situation where you use a hash?
    my %hash; my $count = 0; foreach my $key qw(red blue green yellow black white purple brown orange gray){ $hash{$key}{'index'} = $count; $hash{$key}{'state'} = 0; $count ++; }
    Now do your manipulations on the hash, and set the values accordingly?

    I'm not really a human, but I play one on earth My Petition to the Great Cosmic Conciousness