Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

self-feeding infinite loop

by spx2 (Deacon)
on Aug 18, 2007 at 10:57 UTC ( [id://633455]=perlquestion: print w/replies, xml ) Need Help??

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

ok,ive figgured i could make a loop that would feed itself more and more values in the array that it processes. for that matter i've tried foreach and also map. only in one situation of these four that are listed below, i was succesful,in the one that used foreach and push. why don't the other methods work ? how do you explain their behaviour? P.S.:this is not a test.i am just not at all able to understand why perl behaves this way.

=begin BlockComment # BlockCommentNo_1 my @array = ('a'); foreach (@array) { #push @array,'a';#replace following statement with this one @array = (@array,'a'); print; } =end BlockComment # BlockCommentNo_1 =cut my @array=('a','a'); map { #push @array,'a'; #replace following statement with this one @array=(@array,'a'); print; }(@array);

Replies are listed 'Best First'.
Re: self-feeding infinite loop
by bart (Canon) on Aug 18, 2007 at 11:27 UTC
    It's not clear to me exactly what you're trying to do, but you've sinned against a big no-no:
    Do not modify an array in a foreach loop over the same array.
    What I do, if I don't mind the array being eaten in the process, is something like this:
    $\ = "\n"; # append newline after print @a = 'a'; while(@a) { # there's stuff left in the array my $item = shift @a; # remove it, ready to process print $item; if(rand() < 0.8) { # sometimes add a new item push @a, ('a' .. 'z')[int rand 26]; } }
    Example output:
    a p i n o q e p k

    You see? while doesn't mind at all if the item to loop over, changes under it. foreach does.

    That's one way that you can, for example, replace a recursive implementation of a file digger that does something like File::Find, with an iterative one: just push new directories you encounter while processing a directory, onto the to-do array.

      It's not clear to me exactly what you're trying to do, but you've sinned against a big no-no:
      Do not modify an array in a foreach loop over the same array.
      I argued a few years ago on p5p that the prohibition in the manual is excessive, since the behavior is actually fairly simple and reliable. The only real problem with the technique is that, since p5p has declined to guarantee the current simple, reliable behavior, you have to worry that perhaps a future version of Perl will change the behavior.

      I tried to get p5p to agree to guarantee the current behavior, but it didn't happen.

      In former times there was a similar warning about modifying a hash in the middle of a while (each %hash) loop, even though the hash code had a special case in it to handle that exact use case, since version 5.000. The prohibition in the manual had been added later by someone with more superstition and less understanding of the actual behavior.

      A lot of stuff in the manual is like that, warning you that you must throw salt over your shoulder to avoid the wrath of the Moon God, or whatever.

      I don't think referring to warnings in the manual as "no-nos" or thinking of them in terms of "sin" is a very effective way to handle this sort of issue. We are supposed to be professional adults, not toddlers, and this is supposed to be an engineering discipline, not some mystical rite. Using the language of superstition can only promote superstitious behavior.

        So if you splice an array while using for what do you expect it to do? Especially if you splice elements such that your current index either no longer exists or is in a completly new location? More importantly how many people can agree on what the correct behavior is then?


        ___________
        Eric Hodges

      on the contrary,the code i first posted , namely the first example is showing that using foreach and push works to achieve an infinite loop behaviour.

        Maybe it works, maybe it doesn't. But you're definitely in the territory of "unpredictable behaviour".
Re: self-feeding infinite loop
by graff (Chancellor) on Aug 18, 2007 at 14:14 UTC
    When you do this:
    map { whatever } @array;
    you are taking the initial contents of the array as a list (that is, making a list based on the initial array contents), and map iterates over that list. If anything happens within the map block to change the number of array elements (adding or removing), this has no effect whatsoever on the initial list that drives the map iterations. Consider:
    perl -le '@a=qw/a a/; map{push @a,"A"; print "@a"} @a' # prints: a a A a a A A perl -le '@a=qw/a a/; map{@a=(); print "@a"} @a' # prints two blank lines

    But when you do this:

    foreach (@array) { # add or remove elements in @array... }
    you are asking for trouble. Some variations on that theme might "work" (though maybe not in the manner you intended), but in general it's a bad practice.

    Don't use "for" to iterate over an array if you plan on adding or removing array elements within the loop. Use "while" for that (and be really attentive about making sure you provide the right exit condition(s) to get out of the loop).

Re: self-feeding infinite loop
by stvn (Monsignor) on Aug 18, 2007 at 16:46 UTC

    Honestly, this is so not the way you want to do an infinite list. I won't even get into the why (other responders already have), and Google can tell you the details of it better than I can anyway (although maybe not perl-specific, but the theory at least).

    The following is the more commonly accepted way to go about such a thing.

    my $counter = 0; my $generator = sub { ++$counter }; while (1) { print $generator->(); }
    To re-do your example it would be this:
    my $gimme_an_A = sub { 'a' }; while (1) { print $gimme_an_A->(); }
    Your approach is very memory wasteful since you have an infinitely growing @array, which will eventually consume all available system memory. The above versions never store previous values (which I can only assume you are never using anyway) so they can run in a pretty small and compact memory space.

    -stvn
      mark jason dominus wrote a very good, perl-specific book about this `generator' or `iterator' technique: Higher-Order Perl

Re: self-feeding infinite loop
by ikegami (Patriarch) on Aug 18, 2007 at 18:46 UTC
    You're using an undocumented side-effect of an optimization in some foreach loops. (See an earlier post of mine on different kinds of foreach loops.) There's no guarantee it will continue working in future versions of Perl. Why don't you use a processing queue instead?
    my @todo = ( ... ); while (@todo) { my $item = shift @todo; ... push @todo, ... ... }
Re: self-feeding infinite loop
by shmem (Chancellor) on Aug 18, 2007 at 22:31 UTC
    P.S.:this is not a test.i am just not at all able to understand why perl behaves this way.

    If you iterate over an array @array with foreach, perl doesn't build a list to iterate over, as it does for other cases, i.e. the iterator of the foreach loop goes on until it hits the boundary of the array. If the array changes under the hood, well, more rope then. Consider:

    foreach (@array, 1) { #push @array,'a';#replace following statement with this one @array = (@array,'a'); print; } __END__ a 1

    Since in the above example there's more to the foreach loop than an array, perl builds a temporary list of all elements to foreach, aliasing them, and further changes to the array go by unnoticed. If you have just an array to iterate over, that overhead (of list building) is not done, but array boundaries aren't remembered either. Both behaviours are sensible, since if you have a composed list, there has to be something else to iterate over - hence the temporary list. If you iterate over an array alone, there's no need to build a list.

    BTW, the two statements

    push @array,'a'; @array = (@array,'a');

    do have the same effect with regards to the array iterator in foreach, but the latter is less perlish and produces more overhead, since it copies over the contents of the array every time (the pointer to the array stays the same).

    That said, what are you trying to do? An infinite loop is built much easier like so:

    1 while 1

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: self-feeding infinite loop
by syphilis (Archbishop) on Aug 18, 2007 at 15:14 UTC
    For me, it makes no difference to the actual array whether we do push @array, 'a'; or @array = (@array, 'a');.

    That is, irrsepective of which alternative we comment out in the following script, the output is the same:
    use strict; use warnings; my @array = ('a'); for (@array) { #push @array,'a'; #alternative 1 @array = (@array, 'a'); #alternative 2 print "@array\n"; sleep(1); # so we can absorb what's happening }
    Cheers,
    Rob
      That is, irrsepective of which alternative we comment out in the following script, the output is the same:

      Well yeah, that's not the problem. The question is whether Perl iterates over a list pushed onto its stack or pulls from the AV, one element at a time. That is only determinate behavior in so far as it depends on the particular version of Perl you're running, and p5p has claimed free rein to change that in the future as necessary.

      Plus also:

      sleep(1); # so we can absorb what's happening

      ... makes me say "Huh?"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://633455]
Approved by bart
Front-paged by derby
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2024-04-19 23:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found