Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

for loop localisation bug?

by BrowserUk (Pope)
on Dec 29, 2003 at 14:46 UTC ( #317444=perlquestion: print w/ replies, xml ) Need Help??
BrowserUk has asked for the wisdom of the Perl Monks concerning the following question:

Am I being dumb, or should the value of $n be 50 after the for loop in the following code?

#! perl -slw use strict; $| = 1; print 'Perl version is: ', $]; my $n; for $n ( 1 .. 100 ) { printf "\r%d", $n; last if $n == 50; } print "\nThe value of the counter after the loop is $n"; __END__ P:\test>perl5.6.1 junk.pl8 Perl version is: 5.006001 50Use of uninitialized value in concatenation (.) or string at junk.pl +8 line 14. The value of the counter after the loop is P:\test>junk.pl8 Perl version is: 5.008 50Use of uninitialized value in concatenation (.) or string at P:\test +\junk.pl8 line 14. The value of the counter after the loop is

I'd would assume that this is a bug accept it seems strange that it apparently affects versions back as far as 5.6.1 and hasn't been noticed before.

If this isn't just me having brain fade, could someone test if it still exists on 5.8.2 and/or other platforms?


Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail
Hooray!

Comment on for loop localisation bug?
Download Code
Re: for loop localisation bug?
by cees (Curate) on Dec 29, 2003 at 15:06 UTC

    This is actually the documented behaviour. Check out the perlsyn docs and you will see the followng:

    The "foreach" loop iterates over a normal list value and sets the variable VAR to be each element of the list in turn. If the variable is preceded with the keyword "my", then it is lexically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with "my", it uses that variable instead of the global one, but it's still localized to the loop. This implicit localisation occurs only in a "foreach" loop.

    - Cees

      Thanks cees. That's what I was missing. I'm amazed I never encountered this before, but I guess it just shows how infrequently you need to know at what point a for loop exits.

      It's no problem to re-code this as a c-style for, or any number of other ways, but it did surprise me.

      perl -le" my $n; for( $n=0; $n <=10; $n++ ){ print $n; last if $n == 5 + }; print '$n= ', $n;" 0 1 2 3 4 5 $n= 5

      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      Hooray!

Re: for loop localisation bug?
by talexb (Canon) on Dec 29, 2003 at 15:14 UTC

    You already have one good answer, but another way to test would be to set $n to some value (say, 255) before the loop, and see what prints out.

    --t. alex
    Life is short: get busy!
      That's essentially what he did. $n is uninitialized before and after the loop, but not during. This doesn't change if $n was set to a value (other than the lack of an Unitialized warning).

      Output from my test (in which I changed my $n; to my $n = 255;):

      Perl version is: 5.008001 50 The value of the counter after the loop is 255

Re: for loop localisation bug?
by BrowserUk (Pope) on Dec 29, 2003 at 15:47 UTC

    Out of interest, does anyone know of any other language where the exit value of a pre-existing loop counter variable is abitrarially discarded?

    Does anyone have a good rational for this (IMO broken) implicit DWI(D)M behaviour?

    I realise it isn't going to change, but ...


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!

      I believe the word is rationale
      My guess: could it be this way because otherwise it would interfere with the aliasing properties of foreach?

      $ perl my ($i, $x, $y); for $i ($x, $y) { $i = 5; } print "=$x=$y=\n"; ^D =5=5=

      It has to do with the special way that foreach works. In the statement foreach $n (1, 2, 3) {}, $n actually becomes the value in the LIST on each interation of the loop. It doesn't just get a copy of it in the variable $n. So in your example, $n would actually contain the read-only value 1 on the first iteration of the loop.

      So what would happen if after the loop you tried to assign a value to $n? If it was not localized in the foreach loop, then you would be trying to assign a value to a read-only value, and you would get an error.

      my $n; foreach $n ( 1, 2, 3 ) { } $n = 'foo';

      If $n was not localized, you would get a "Modification of a read-only value attempted ..." error in your code.

      A better explaination of this issue can be found here in the perl5-porters archive.

      - Cees

        Just to add a term: the name for this kind of behaviour is aliasing. Which is a concept that people seem to have little trouble using in practice, but much harder to understand and explain. Some languages have no concept of aliasing at all. In perl @_ is aliased, as are the iterator in a foreach loop, grep, map and sort. In Pascal "var" parameters are essentially aliases, as are normal parameters in VB (byref parameters). Its my understanding that C has no aliases.

        I find a useful way of thinking about aliases is that they are like references that need not be dereferenced.


        ---
        demerphq

          First they ignore you, then they laugh at you, then they fight you, then you win.
          -- Gandhi


      In both, C and Pascal, the value of the loop counter variable after leaving the loop (however) is undefined. This is to allow the compiler to optimize its loop construct, as some compilers only have one internal loop construct and convert the syntactic candy of for, while and their ilk to it - or at least I believe that's why it was defined as undefined/implementation specific.

      perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web

        I beg to differ, at least as far as C is concerned.

        #include <stdio.h> void main() { int n; for( n=0; n<10; n++ ) { printf( "%d\n", n ); if( n == 5 ) break; } printf( "Residual value: %d\n", n ); } __END__ P:\test>bcc32 test.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland test.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland P:\test>test 0 1 2 3 4 5 Residual value: 5

        And even in languages that allow the loop variable to be declared within the for loop initialiser statement, it's scope is not that of the for loop body, but that of the enclosing block.

        From p.38 of "Programming in C++ by Stephen C. Dewhurst & Kathy T. Stark"

        Declarations are statements and can appear any place a statement can within a block. This flexibility allows names to be declared and initialised at the point of their first use. For example, the first clause in a for loop control is a statement so a loop index can be declared there.
        for( int i = 0; i < limit; i++ ) { // etc. } return i;

        The scope of the name extends from its point of declaration to the end of the enclosing block. Notice that in this example, i is still available after the loop terminates. It goes out of scope at the end of the block that contains the for statement.

        I don't have a Pascal reference handy, but I seem to recall (from a very long time ago) that it's for loop acted similarly.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Hooray!

      I find calling something broken before you even know the reason to be.. daring to say the least.

      If it were "fixed", a number of things we're used to doing with the aliasing property of foreach such as s/foo/bar/ for @array; would cease to work. Frankly I wouldn't have it another way - more than a few times have I found the persisting iteration variable value behaviour of FOR loops in Pascal and C annoying, and more than a few times have I found the aliasing behaviour of Perl's foreach useful.

      If you need the value, then just do manually what Perl avoids to: copy it to a variable from the surrounding scope on each iteration.

      Makeshifts last the longest.

        Frankly I wouldn't have it another way - more than a few times have I found the persisting iteration variable value behaviour of FOR loops in Pascal and C annoying, and more than a few times have I found the aliasing behaviour of Perl's foreach useful.

        Just to throw my two pennies in on this one. I feel that that's merely the, "feature set," of the different languages. There are different instances where I would rather have the iteration variable persist and other times where I don't want it to persist.

        I guess what I'm saying is that not every language can meet the needs of every situation nor make every developer happy. I can't vouch for anyone else, but that's why I have so many different languages installed. Different tools for different needs.

        ----
        Is it fair to stick a link to my site here?

        Thanks for you patience.

        Fair enough. My choice of the word broken, even when expressed only as 'my opinion', was probably rash.

        I should have said, unintuative, unhelpful or (as I did) Doing What I Didn't Mean.

        By using a predeclared variable as the loop counter, I am explicitly attempting to retain the exit value of loop counter. This behaviour is implicitly overriding that attempt -- in order to provide a behaviour that is available to me explicitly, by declaring the loop variable within the for loop construct for my $n ( ... ) -- and hence not doing what I mean.

        I understand, utilise, and admire the design of the aliasing done by the for loop, and I agree with you that it is a useful feature that is DWIM in most instances. The fact that I couldn't explicitly override it when I tried to, took me by surprise, and (still) leaves me wishing that I could.

        I guess that this is a throwover from pre-lexical days rather than an explicitly sought-after design feature. Then, someone will pop up and tell me that I guess wrong:)

        In any case, I didn't mean to indicate that it should be fixed at this late stage, just that I would prefer that implicit behaviours would take a back seat to explicit behaviours. In most cases, perl gets that right (IMO), but in this instance, probably as a matter of history, I think it gets this wrong.

        No biggy, just an embarrased reaction to an implicit behaviour (that I had never before encountered) overriding my explicit attempt to define WIM. Despite reducing the problem to it's simplest in a test script and spending half an hour staring at it to see what I was doing wrong, it turned out to be "user error".

        I didn't even consider looking it up, because based upon both my intuition, and experience of other languages, the behaviour I was seeing "couldn't possibly be correct". The fact that it is documented as working this way means it is 'correct', but I would never have guessed that it would be so.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Hooray!

      Turing goes a step further and explicitly forbids the use of an iterator variable outside of the loop its used in. In that language you dont have to declare an iterator, and it disappears as soon as the loop terminates.

      And I believe the reason for this logic is that the value can be undefined depending on how the compiler handles it. Ie:

      for my $i (0..10) { print "$i\n" }

      Could be optimised into ten print statements with no iterator at all.


      ---
      demerphq

        First they ignore you, then they laugh at you, then they fight you, then you win.
        -- Gandhi


        Ah! But what about

        my $i; for $i ( 0 .. 10 ) { last if $i == 5; } print $i;

        This could equally be optimised to remove the loop, but optimising the iterator away would be an error. In this case, the modification of the iterator is the primary purpose of the loop.

        Of course, this is grossly simplified, the break condition would not be a comparison against a constant, and in the original scenario, the iterator was being used as the index into an array. In other words, the loop was searching an array for a particular value and the desired outcome is the index of the array at which it is found.

        This isn't exactly an unusual use of a for loop

        How would this be done in Turing?


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Hooray!

Re: for loop localisation bug?
by mpeppler (Vicar) on Dec 29, 2003 at 18:25 UTC
    Just to add my 2 cents - it's important not to confuse
    for $n (1 .. 100)
    with
    for($n = 1; $n <= 100; ++$n)
    as the former is an alias for foreach with the localization of the loop variable enabled (for the reasons that have been cited above), and the latter a straight loop counter similar in semantics as C's for() loop.

    This reminds me of being bitten by the following several years ago:

    do { ..... last if $some_condition; } while($some_other_condition);
    A C programmer would think nothing of this loop. It's a straight do/while loop with a break (or its perl equivalent) statement, right?

    Wrong. last/next doesn't work in this situation, because it isn't really a do/while loop, but rather a do BLOCK with a post condition (the while($some_other_condition) could just as well have been an unless() or if()).

    Michael

      and the latter a straight loop counter similar in semantics as C's for() loop.

      The latter is essentially syntactic sugar for a while loop. Consider:

      for (my $n=1;$n<=10;$n++) { ... } # essentially becomes { my $n=1; while ($n<=10) { ... } continue { $n++; } }

      Its my understanding that the continue part of while was specifically introduced to provide the c-style for loop behaviour.


      ---
      demerphq

        First they ignore you, then they laugh at you, then they fight you, then you win.
        -- Gandhi


      Your post gave me some ideas. How about emulating BrowserUk's foreach, for devil's sake, with something like:

      my $n; for (my @tmp = (LIST); @tmp ? ($n = shift @tmp) || 1 : 0;) {...} print "Last \$n is: $n\n";

      The tradeoff is that you're iterating over copies, and you won't get aliasing.

Re: for loop localisation bug?
by Zaxo (Archbishop) on Dec 29, 2003 at 19:26 UTC

    Maybe it won't seem so much like a bug if it is demonstrated in a more familiar form:

    local $_ = "I live!\n"; for (1..100) { printf "\r%d", $_; last if $_ == 50; } print "$/The value of 'this' after the loop is $_$/";

    After Compline,
    Zaxo

      I'd counter that, as your version is coded, it is correct behaviour, but if it was coded as

      local $_ = "I live!\n"; for $_ (1..100) { printf "\r%d", $_; last if $_ == 50; } print "$/The value of 'this' after the loop is $_$/";

      It wouldn't be correct as I have explicitly overridden the implicit behaviour.

      At at this point I'm supporting what I consider would be intuatively correct behaviour. Perl ain't never gonna work this way, and I accepted that way back with cees first post:)


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
      Hooray!

Re: for loop localisation bug?
by Roger (Parson) on Dec 29, 2003 at 23:34 UTC
    This reminds me of a reply I wrote, node 309078, on a similar question "for 'surprises'" not long ago.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chilling in the Monastery: (5)
As of 2014-09-20 10:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (158 votes), past polls