Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

loop surprise

by morgon (Priest)
on Apr 03, 2018 at 13:23 UTC ( #1212245=perlquestion: print w/replies, xml ) Need Help??

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

Hi,

can someone explain this behaviour to me:

use strict; my $i = "whatever"; for $i (1..10) { last if $i == 7; } print "$i\n"; # prints "whatever"
I had expected that within the loop the previously declared variable would be used and with each iteration it would be assigned a new value, so that after the loop it should have the new value 7.

But that's not how it works.

The loop does not seem to change the value of $i at all - but why?

Many thanks!

Replies are listed 'Best First'.
Re: loop surprise
by Athanasius (Bishop) on Apr 03, 2018 at 13:34 UTC

    Hello morgon,

    From perlsyn#Foreach-Loops (first paragraph):

    The foreach loop iterates over a normal list value and sets the scalar 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 localization occurs only in a foreach loop.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Ok, that explains it, but I don't quite get the rationale for doing it this way...

      If I wanted a localized variable I write the loop as "for my $j (...)".

      If I explicitly use "for $i" I make clear my intention of using a previously introduced variable.

      Why perl then insists on knowing better and localizing it is counter-intuitive to me and at the moment I don't see any reason why it should be implemented like that (but there probably is one)...

        Hi morgon,

        I wouldn't say it's that Perl "knows better" so much as that it's a convenience for being able to modify the data you're iterating over.

        Note that, because it's only a foreach loop that acts this way, and for is this context is just an alias for the same thing, if you want the behavior you were expecting you can use the 3-arg form of a for loop:

        use strict; my $i = "whatever"; for ($i = 1; $i <= 10; $i++) { last if $i == 7; } print "$i\n"; # prints "7" instead
        And believe it or not, you could even make it a 3-arg foreach loop (which is kinda counter-intuitive, but again, "for" and "foreach" are interchangeable):
        use strict; my $i = "whatever"; foreach ($i = 1; $i <= 10; $i++) { last if $i == 7; } print "$i\n"; # still prints "7"
        say  substr+lc crypt(qw $i3 SI$),4,5
        > get the rationale for doing it this

        This was often discussed already, have a look into the archives if you want more details.

        IIRC it's because this behaviour of localizing the loop var is older (read Perl4) than lexicals.

        Rule of thumb: never try to reuse an outer var as loop var in foreach... see also PBP advice to always for my $x (...) .

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Wikisyntax for the Monastery

        ) it leads to very confusing results if the loop creates subs closing over the loopvar.

Re: loop surprise
by toolic (Bishop) on Apr 03, 2018 at 13:35 UTC
    This is documented in Compound Statements
    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 localization occurs only in a foreach loop.

    The variable does change value within the loop, as you can see with print:

    for $i (1..10) { last if $i == 7; print "in loop $i\n"; }
[OT]: Re: loop surprise (unsurprising D foreach)
by AnomalousMonk (Chancellor) on Apr 03, 2018 at 21:17 UTC

    <tangent>
    Interestingly (well, to me anyway), the D language also has a Perlish foreach (and foreach_reverse) loop that is distinct from its C-ish for-loop. The names for and foreach are not synonymous, however, as they are in Perl, perhaps thereby avoiding some confusion. In general, D, with its intimate embrace of associative arrays, foreach loops and a few other features, has for me an evocative resemblance to Perl — well, if you squint your eyes a bit and look at it from just the right angle.
    </tangent>


    Give a man a fish:  <%-{-{-{-<

      we are not amused
Re: loop surprise
by Your Mother (Bishop) on Apr 03, 2018 at 17:42 UTC

    Probably making the (undeclared) loop $i a syntax error would be best but it looks like the ship has sailed. An error was what I would have guessed seeing your code because it never in a million years would occur to me to repurpose a variable that way and I'd definitely be frustrated with any code, and the person responsible, I had to work with that did it.

      Personally, I wouldn't break out on a condition related to the value of $i itself in such a loop, but I often run across circumstances such that I want to use the loop variable to store the last condition that was valid, when looping through a list of valid or invalid conditions. For example,

      my $condNum = 0; for $condNum ( 1 .. 10 ) { forceSituation( $conditions[condNum] ); my $result = measureSituation(); push @results, $result; last if $result->is_error(); more_manipulation(); # possibly do more stuff for this condition, +but only if it's not an error } datalog( value => $condNum, llim => 9.9, hlim => 10.1, name => 'number + of situations tested' ); datalog( value => fn(@results), llim => -2.7182818, hlim => 3.1415926, + name => 'result of those situations' )
      ... That loop wouldn't work in perl. But it doesn't seem an abuse of the loop counter, to me, to use the loop counter as the indicator of how many situations were actually tested. The code above seems more natural to me than
      my $lastCondNum = 0; for my $condNum ( 1 .. 10 ) { ... $lastCondNum=$condNum, last if $result->is_error() ... } ...
      Why require a second variable just to store the last condition number it happened to be in, when the loop variable seems a natural storage device for that information?

      (In these situations, I'm generally in a hardware-specific language, not Perl, so that one annoyance of Perl doesn't usually affect me. But there are a plethora in the hardware-specific language that I wish it did more like Perl does, so not a fair balance, in my opinion. :-( )

        my $condNum = 0; for $condNum ( 1 .. 10 ) { forceSituation( $conditions[condNum] ); my $result = measureSituation(); ...; last if $result->is_error(); ... } datalog( value => $condNum, ...); ...

        But in that case, why not use a C-style loop

        c:\@Work\Perl\monks>perl -wMstrict -le "use constant N_MAX => 10; ;; my $nCond; ;; CONDITION: for ($nCond = 1; $nCond <= N_MAX; ++$nCond) { print $nCond; last CONDITION if $nCond >= 3; } ;; print qq{only made it to condition $nCond} if $nCond < N_MAX; " 1 2 3 only made it to condition 3
        (as suggested here)?


        Give a man a fish:  <%-{-{-{-<

        Okay, you make a good case. I can see that as being useful. But to my mind the loop is the scope and the for statement is the loop definition and therefore its scope. I've never felt/noticed the lack you highlight.

        I find it odd that I don't remember ever suffering from this need. I'm sure I've done something similar, but perhaps I've unconsciously soaked up that lesson long ago, and just "know" to use a second variable, or even a while loop.

        -QM
        --
        Quantum Mechanics: The dreams stuff is made of

      Probably making the (undeclared) loop $i a syntax error would be best but it looks like the ship has sailed.

      Maybe so, but perlcritic spots it just fine:

      $ perlcritic 1212245.pl Loop iterator is not lexical at line 5, column 1. See page 108 of PBP +. (Severity: 5)
Re: loop surprise
by ikegami (Pope) on Apr 04, 2018 at 15:25 UTC

    my didn't always exist. This feature allows you to simplifify

    local $e; for $e (LIST) BLOCK

    to

    for $e (LIST) BLOCK

    These days, it makes no sense to do anything but

    for my $e (LIST) BLOCK
    or
    for (LIST) BLOCK
Re: loop surprise
by karthiknix (Sexton) on Apr 05, 2018 at 10:28 UTC

    My keyword is for local scoping of a variable. It says $i is a local variable, but it is global to the parent class. $i you have used in for loop is local to for loop and it does not assign any value to it. You are iterating the loop for from 1 to 10 and it assign the value to a temporary variable in your case it is just $i. Below is an example how iterations works in a loop

    for(1..10) { print "yes" if(/7/); }

    in the above examples $_ is used a temporary variable and it gets null when it is out of for Loop. I hope it make you clear how Variables work in loop

Re: loop surprise
by Anonymous Monk on Apr 03, 2018 at 15:51 UTC
    In any programming language, "loops are usually special." If you need to capture the value of a loop-control variable beyond the boundaries of the loop, use a separate variable for that purpose. (Remember to initialize it to some value before the loop begins, in case the loop doesn't run at all!) It is very common for various implementation quirks to be used with regard to loops, in the very-important name of efficiency.
      It is very common for various implementation quirks to be used with regard to loops, in the very-important name of efficiency.

      Rather than efficiency, I think reliability (or perhaps one should better say coherence) is the key concern. If a topicalized (i.e., localized and aliased) Perl-style loop iterator were left un-de-localized upon exit from the loop, to what would it be aliased? An arbitrary element of some named or referenced array? An item in a temporary list, perhaps a literal (i.e., something unwritable), or the (writeble) return value of a function call? (This point has been touched upon in other replies.) Such a state of affairs seems like a recipe for some very perplexing bugs.

      IOW, if not de-localized, exactly what is the nature of the thing to which  $_ would remain aliased after loop exit in this code:

      c:\@Work\Perl\monks>perl -wMstrict -le "sub F { return 4; } sub G { return 5; } sub H { return 6; } ;; for (F(), G(), H()) { ++$_; printf qq{$_ }; } " 5 6 7
      And why would one want to do that?


      Give a man a fish:  <%-{-{-{-<

        if not de-localized, exactly what is the nature of the thing to which $_ would remain aliased after loop exit

        Aliasing is an implicit reference to the object that is aliased. Like any referenced object, it would remain until all references to it have been removed.

        As for why, to see what was last processed. Sure, you could use an extra variable, but doing so adds more opportunities for introducing bugs.

        Besides, according to Larry, laziness is a virtue.

        Amusement?
    A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2019-06-19 01:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Is there a future for codeless software?



    Results (83 votes). Check out past polls.

    Notices?
    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!