Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

foreach-loop-local var in sub

by warshall (Novice)
on Jan 21, 2013 at 15:48 UTC ( #1014473=perlquestion: print w/ replies, xml ) Need Help??
warshall has asked for the wisdom of the Perl Monks concerning the following question:

Obviously a newbie question, but... Given the following program:
foreach (0, 1) { my $i = $_; sub my_print () { print $i; } my_print; }
Why does it output "00"? I would think it should output "01". Thanks!

Comment on foreach-loop-local var in sub
Download Code
Re: foreach-loop-local var in sub
by blue_cowdawg (Prior) on Jan 21, 2013 at 15:55 UTC

    First off you've declared the sub as having no arguments. Secondly within the curly braces of the sub you have changed context and $i is uninitialized within that context and therefor you are going to print zeros for each iteration.

    Here is code that does what you think it should:

    foreach (0, 1) { my $i = $_; sub my_print { my $i = shift; print $i; } my_print( $i); }

    Now... you could have saved yourself a lot of pain if you had

    use strict; use warnings;
    at the beginning of your code. Bailiff, lock him up.


    Peter L. Berghold -- Unix Professional
    Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
      you have changed context and $i is uninitialized within that context and therefor you are going to print zeros
      Not exactly true. Try with
      foreach (qw(a b)) {
      Update: Do not use numbers for testing behaviour, see Re^3: variable declaration question.
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
            Not exactly true. Try with

        Eh? What do you mean by that? Trying the OPs code with your mod still gives the result that they are not after.


        Peter L. Berghold -- Unix Professional
        Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
Re: foreach-loop-local var in sub
by choroba (Abbot) on Jan 21, 2013 at 16:02 UTC
    Defining a named sub in a loop is not doing what you think. The sub is defined just once, using the $i of the first iteration. On the next iteration, a new $i is created by my, but the sub is not redefined and still references the previous $i. It is better to use parameters to pass values to subroutines.

    If you remove my (and, under strict, replace it with our), the intended behaviour will occur, using a global variable.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
      Thank you. this answered my question. I didn't know about "our" (and still don't, but at least now I know what I should be looking for). -warshall
        what I should be looking for
        I would recommend looking for subroutine parameters, rather. See perlsub for details.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

        I didn't know about "our" (and still don't, but at least now I know what I should be looking for)
        Nooo!!! You should not be looking at our! Lexical variables (my) are what you should be learning about first and they should be preferred to global variables (our) in almost all cases.

        Given you are a Perl beginner, you need to master the basics of scoping and how to write subroutines. Start by defining all your subroutines at the top of the file and pass parameters to each subroutine. Do not succumb to the evil of having subroutines use global variables. Instead think about your code, what each subroutine does and what it needs, and pass parameters to each subroutine for use as local variables within the subroutine. For example:

        use strict; use warnings; sub my_print { my $i = shift; print $i; } foreach my $j (0, 1) { my_print($j); }

        Further reading from the Perl monks Tutorials section:

        As for learning Perl, take a look at learn.perl.org and Perl Tutorial Hub. Also, be sure to refer to the Perl online documentation. Good luck!

        I think you are searching for anonymous subroutines and closures, but given that you indeed seem like a programming/Perl newbie, you shouldn't be venturing there and instead heed to the advice given in 1014512 above.

Re: foreach-loop-local var in sub
by tobyink (Abbot) on Jan 21, 2013 at 16:06 UTC

    sub definitions within control structures don't really work how you want them to work. Your my_print function only gets defined once; not each time around the loop. If you want to redefine the sub each time around the loop, use an anonymous sub (a.k.a. closure; coderef) - which can be given a name by assigning it to a glob.

    This works:

    use strict; use warnings; no warnings 'redefine'; # stub to declare sub name, allowing it to be used # as a bareword later on sub my_print (); foreach my $i (0, 1) { *my_print = sub () { print "[$i]\n"; }; my_print; }
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: foreach-loop-local var in sub
by Athanasius (Monsignor) on Jan 21, 2013 at 16:33 UTC

    Hello warshall, and welcome to the Monastery!

    Just to reinforce the explanations given by choroba and tobyink, here is what the Camel Book (4th Edition, page 358) says about Nested subroutines:

    If you are accustomed (from other programming languages) to using subroutines nested within other subroutines, each with their own private variables, you’ll have to work at it a bit in Perl. Named subroutines do not nest properly, although anonymous ones do.9

    9 To be more precise, globally named subroutines don’t nest. Unfortunately, that’s the only kind of named subroutine declaration we have. We haven’t yet implemented lexically scoped, named subroutines (known as my subs), but when we do, they should nest correctly.

    Hope that helps,

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

      FYI, Perl 5.18 will have experimental implementations of my sub, state sub and our sub. our sub is effectively the same as the existing sub keyword but can also be used to hide my sub subs, a la:

      use 5.010; our $foo = 42; my $foo = 99; say $foo; # says 99 our $foo; # "hides" my $foo say $foo; # says 42
      package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

        I'm looking forward to my subs!

Re: foreach-loop-local var in sub
by Anonymous Monk on Jan 21, 2013 at 16:41 UTC
    This also reinforces the need to always specify use strict; use warnings; in everything you do. Perl won't generate error- or warning-messages when maybe you think it should.
      In this particular case, the code runs without complaints even under strict and warnings.
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: foreach-loop-local var in sub
by LanX (Canon) on Jan 21, 2013 at 17:51 UTC
    ... strange, in your case I would expect to see a warning like "variables won't stay shared"...

    These kind of problems are easily avoided by either using lexical subs:

    use strict; use warnings; foreach (0, 1) { my $i = $_; my $print = sub { print $i; }; $print->(); # 0,1 }

    or just not expecting to be able to easily redefine package subs at runtime, they are defined only once at compile time:

    use strict; use warnings; my $i; sub my_print { print $i; }; foreach (0, 1) { $i = $_; my_print(); # 0,1 }

    Anyway, while there are good use-cases for closures, I'd expect a beginner to go easy and pass arguments normally :

    use strict; use warnings; sub my_print { my ($i)=@_; print $i; }; foreach (0, 1) { my_print($_); # 0,1 }

    Cheers Rolf

      I get strange results from a slight variation:
      use strict; use warnings; my $i = 6; sub my_print { print $i; }; for ($i = 1; $i < 3; $i++) { my_print(); print " (C-Style:\$i==$i)\n"; } for $i (qw|x y|) { my_print(); print " (Perl Style:\$i==$i)\n"; }
      Output:
      1 (C-Style:$i==1) 2 (C-Style:$i==2) 3 (Perl Style:$i==x) 3 (Perl Style:$i==y)
      Not that I would write code like this, but how come the "perl style" does not update the "$i" the sub sees ?

                   Most people believe that if it ain't broke, don't fix it.
              Engineers believe that if it ain't broke, it doesn't have enough features yet.

        From perlsyn#Foreach Loops

               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. 
        

        If you also check the references you will see that the '$i' are pointing to different locations.

        use strict; use warnings; $,=","; $\="\n"; my $i = 6; sub my_print { print $i,\$i; } ; for $i (qw|x y|) { my_print(); print " (Perl Style:\$i==$i)",\$i,"\n"; }
        OUTPUT
        6, SCALAR(0x8fa4e38) (Perl Style:$i==x), SCALAR(0x8f86760), 6, SCALAR(0x8fa4e38) (Perl Style:$i==y), SCALAR(0x8fa4da8),

        That's why PBP says to always use lexical loop vars in foreach!

        Cheers Rolf

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (8)
As of 2014-08-22 11:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (156 votes), past polls