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

I got an unexpected behavior in the value of a global variable used as an iterator in a "for" loop: It's current value is not accesible from a subroutine called on each iteration.

#!perl use strict; use warnings; my $i = 0; for $i (1 .. 3) { show(); } sub show { print "$i\n"; }

I was expecting 1 2 3, but got 0 0 0 !!!

If I add a "my" in the "for" loop, I am saying that the new $i variable has it's own scope, and I understand that the sub cannot see it, but without a "my" in the "for", shouldn't the scope of $i be the same everywhere?

If I don't declare and initialize the $i variable, I get an error unless I also add a "my" in the "for" loop. That says it is the same variable. Why to add an extra scope to it during runtime?

A workaround is to declare another iterator variable in the "for" and assign it to $i as the first step in the loop...

  • Comment on Access a global variable from a subroutine when used as "for" iterator
  • Download Code

Replies are listed 'Best First'.
Re: Access a global variable from a subroutine when used as "for" iterator
by swl (Prior) on Dec 20, 2023 at 21:34 UTC

    The first para of this section of perlsyn is relevant here: https://perldoc.perl.org/perlsyn#Foreach-Loops, specifically:

    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 for non C-style loops.

    So the variable is localised inside the loop, but your sub wrapped over the original, non-localised version which is not updated.

    It's a bit like generating a new my $i inside a new sub, e.g. inner_sub the code below.

    There is perhaps an issue of clarity in the docs given one cannot call local on a lexical variable but that text might pre-date the introduction of my declarations (and localization might not be referring to the local keyword).

    #!perl use strict; use warnings; our $i = 0; for (1 .. 3) { show(); } inner_sub(); sub inner_sub { my $i = 8; show(); }; sub show { print "$i\n"; }
      I'm surprised it's not a warning. Very unexpected behavior...
Re: Access a global variable from a subroutine when used as "for" iterator
by LanX (Saint) on Dec 20, 2023 at 22:25 UTC
    The implicit local-ization of foreach-loop vars comes with some magic side effects.°

    If you really want that closure ( NB: it's NOT a "global variable" ) better use other loops like c-style for (;;) or while() or just do like you said and copy the loop-var to the closure var.

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

    °) IMHO stemming in compatibility to Perl4 which didn't have my

Re: Access a global variable from a subroutine when used as "for" iterator
by GrandFather (Saint) on Dec 20, 2023 at 23:01 UTC

    swl has given a great answer that should help you understand the scope of "iterator" (for loop) variables in Perl for loops, but the real issue here is smelly code! Your sub show would be much better to take $i as a parameter so it is clear everywhere show() is used what is being shown:

    for $i (1 .. 3) { show($1); } sub show { my ($i) = @_; print "$i\n"; }

    That not only fixes the immediate issue, but makes the code clearer and easier to maintain.

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      While I don't think the OP really wanted a closure and knows their purpose, I wouldn't call them "smelly" if used appropriately.

      With this short demo we can't really guess the overall intention.

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

        Closures are not generally smelly, but global variables almost always are. My impression is the OP was aiming for a global variable rather than a closure. Very likely if a closure is intended by an OP the fact is mentioned because that has a strong influence on the expected behavior of the code. No mention, no closure.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        You are right... my demo was to present what I found. In the real code, the equivalent to show() function receives a parameter, but under certain conditions, I wanted it to default to the iterator.

Re: Access a global variable from a subroutine when used as "for" iterator
by GrandFather (Saint) on Dec 21, 2023 at 20:48 UTC

    Hi vitoco. I'm afraid you have unwittingly sewn great confusion with your question. Usually when we speak of an "iterator" we mean an object (rather than a simple variable) that is used to return some result each time it is used. Very often this is achieved using things called "closures" which is why there has been all this talk of closures in most of the answers you received. Closures are very strange things until you understand them. Let me show you:

    use strict; use warnings; use feature 'say'; my $it = MakeCounter(); say $it->() for 1 .. 3; sub MakeCounter { my $count = 0; return sub {return ++$count;}; }

    Prints:

    1 2 3

    Each time MakeCounter is called a new $count variable is created. The anonymous sub that is returned then holds on to and uses that specific instance of $count. The anonymous subroutine "closes" over the $count variable. The thing being closed over need not be a simple scalar variable. Often it will be an array or hash or file handle or database statement handle or even an iterator.

    This doesn't help solve your problem, but maybe helps understand where the "closure" comments are coming from.

    The more usual term for the variable used in a for loop (both Perl and C types) is "loop variable".

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      I'm afraid you have unwittingly sewn great confusion with your question. Usually when we speak of an "iterator" we mean an object (rather than a simple variable) that is used to return some result each time it is used. ... The more usual term for the variable used in a for loop (both Perl and C types) is "loop variable".

      I'm sorry about that. English is not my mother language and I use what I've read somewhere else. There are some sources that names the loop variable as the "iterator", like here.

      Thanks about the "closures" explanation. That is something I've never tried...

        I suspect the author of that site may not have English as their first language either. In itself that is not any sort of issue. However the site has very worrying omissions that could cause rather unhappy days. For example the mentions of sigils ('$', '@', '%' ...) are in relation to variables, but that is misleading. Sigils are used to denote the type of a result so when they are used with variables they denote the type the of the variable in an expression. So far so good, but consider:

        my @array = (1 .. 5); my $arrayRef = \@array; say "$arrayRef: @$arrayRef";

        which prints for me (your result will be different):

        ARRAY(0x7bc278): 1 2 3 4 5

        In the say @ returns the contents of the scalar variable arrayRef as an array.

        The much worse omission is that context plays a vital part in understanding what many Perl statements mean. Context seems not to be mentioned at all by the site. Consider:

        my @array = (1, 2, 3, 4, 5); my $count = (1, 2, 3, 4, 5); say "$count: @array";

        which prints (the first three lines are warnings btw):

        Useless use of a constant (2) in void context at D:\Scratch~~\PerlScra +tch\noname.pl line 6. Useless use of a constant (3) in void context at D:\Scratch~~\PerlScra +tch\noname.pl line 6. Useless use of a constant (4) in void context at D:\Scratch~~\PerlScra +tch\noname.pl line 6. 5: 1 2 3 4 5

        The site is a nice light weight introduction to Perl, but it misses important concepts and is somewhat dated. For example it discusses the "given statement" which is now deprecated and will be removed in future versions of Perl. Treat the material on the site with caution!

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
        There are some sources that names the loop variable as the "iterator"

        Indeed and that's probably an unfortunate choice of term in that article also but context is an important thing.

        FWIW, many years ago when I was learning to code we were taught that the variable which increments on each turn through a loop was termed the "control variable". That is the name I still use today. You may see it used for other meanings but always (so I've found) where its value performs some equivalent mechanism. Nobody has complained to me about this name when I've used it. Up to now, anyway. :-)


        🦛

Re: Access a global variable from a subroutine when used as "for" iterator
by johngg (Canon) on Dec 20, 2023 at 21:26 UTC

    You could use a subroutine reference and form a closure over $i using a do block so that it remains in scope outside the block.

    johngg@aleatico:~$ perl -Mstrict -Mwarnings -E 'say q{}; my $rcShow = do { my $i = 1; sub { return $i ++ }; }; say $rcShow->() for 1 .. 3;' 1 2 3

    I hope this is helpful.

    Cheers,

    JohnGG