Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Strictly nested sub warnings

by raybies (Chaplain)
on Oct 05, 2010 at 12:16 UTC ( #863583=perlquestion: print w/ replies, xml ) Need Help??
raybies has asked for the wisdom of the Perl Monks concerning the following question:

I wrote a script that works okay, then I decided to add the "use strict" and "use warnings" to it, and all #$%^@?! has broken loose. Took me half the day to fix everything it was complaining about, but in the process I found two bugs in my script (one of which made me wonder why my script was working in the first place... ah, wondrous perl!), so I suppose it's worth the effort.

Though honesty I wish there were a way to tell perl that the outer most variables were okay not to use the package::variable name... because sticking $main::varname in some of my more specific subs, where I know what I'm doing is really tedious.

Anyhoo... I have one warning left...

Here's a very abbreviated example of the problem:

#!/usr/bin/perl use strict; use warnings; use File::Find; sub SymLinkFind($); my %params; $params{recurseDir} = "."; SymLinkFind(\%params); print "Found ", $params{numoLinks}, " Symbolic Links...\n"; sub SymLinkFind($) { my $reparams = shift; my @SymLinks; sub inlineFind { my $namey = $File::Find::name; if (-l) { push @SymLinks, $namey; } } find \&inlineFind, $reparams->{recurseDir}; $reparams->{Links} = \@SymLinks; $reparams->{numoLinks} = scalar(@SymLinks); return; }

The problem is I get a warning that says:

Variable "@SymLinks" will not stay shared at ./test.pl line 21.

I think this is telling me that were I to attempt to call FindSymLinks again, that due to the internal variable being used in the nested subroutine (named inlineFind) that it would behave as though it were (c equivalent to) a static variable, retaining its values from the last time the function was called, rather than creating a fresh new @SymLinks array.

Is this a correct understanding of the warning? And is there a way to get rid of this warning?

I read somewhere that if I were to assign the sub inlineFind to a variable and provide the reference to that sub through the variable assignment, that it would do some kind of magic and fix the issue, but I haven't been able to figure a way to create a perl variable that references the nested sub.

Any ideas from the gurus?

Thanks,

--Ray

Comment on Strictly nested sub warnings
Download Code
Re: Strictly nested sub warnings
by JavaFan (Canon) on Oct 05, 2010 at 13:03 UTC
    Though honesty I wish there were a way to tell perl that the outer most variables were okay not to use the package::variable name... because sticking $main::varname in some of my more specific subs, where I know what I'm doing is really tedious.
    Do you mean use vars qw[$foo @bar %baz];?
Re: Strictly nested sub warnings
by JavaFan (Canon) on Oct 05, 2010 at 13:09 UTC
    The problem is I get a warning that says:

    Variable "@SymLinks" will not stay shared at ./test.pl line 21.

    Did you look up the warning in perldiag? If so, which part isn't clear? If not, you should.

      Thanks. I found more info about the warning...

      Curious, but is perldiag a program or part of the built-in help? Cuz when I type perldiag at the linux prompt it does nothing.

      I googled perldiag, and found this page, that helped me understand that I need to create the sub anonymously, that way it's created at runtime, rather than at compile time...

      SO, fwiw, I was able to fix my warning by assigning the nested function named inlineFind to a variable anonymously like the following:

      #!/usr/bin/perl use strict; use warnings; use File::Find; sub SymLinkFind($); my %params; $params{recurseDir} = "."; SymLinkFind(\%params); print "Found ", $params{numoLinks}, " Symbolic Links...\n"; sub SymLinkFind($) { my $reparams = shift; my @SymLinks; my $subhold = sub { #<--this line changed my $namey = $File::Find::name; if (-l) { push @SymLinks, $namey; } }; #<--remember ; to complete variable assign find $subhold, $reparams->{recurseDir}; # <--use variable instead of + named function. $reparams->{Links} = \@SymLinks; $reparams->{numoLinks} = scalar(@SymLinks); return; }

      And that got rid of the warning. So is there any great impact on the performance of this, if it is compiled at runtime? I didn't see any in running my simple example.

      Thanks,

      --Ray

        It's one of the documentation pages.

        man perldiag perldoc perldiag perl -Mdiagnostics script.pl

        perldiag

        Nested named subs are not supported by Perl. For starters, the nested sub wouldn't be private. Secondly, you'll get weird errors. (You're notified if the second can happen by "will not stay shared" warnings.)

        Curious, but is perldiag a program or part of the built-in help? Cuz when I type perldiag at the linux prompt it does nothing.
        Neither. Perl does not come with built-in help. Instead, perl comes with a gazillion manual pages - each of them listed in the perl manual page.

        You have typed man perl from your Linux prompt at least once, didn't you?

        So is there any great impact on the performance of this, if it is compiled at runtime?

        It's compiled at compile time just like a named function. You get a new version of it every time you execute the code (so it can bind to an enclosing lexical scope), but the actual opcodes Perl 5 executes within the body of the function stay as they are.

        You seem to be writing Perl in an idiosyncratic, verbose, almost baroque, style. To do something very simple, namely find all symbolic links under a given directory, you've got a lot of scaffolding. Perhaps the code has been taken out of context, but I feel your "input-output" parameter hash serves only to obscure the intent of the function. Good functions have few, and clearly specified, input and output. Since Perl arrays already know how many elements they contain, storing the number of elements in a separate numoLinks attribute is redundant and a violation of DRY. Anyway, FWIW, I present a shorter, more Perlish version of your subroutine:

        use strict; use warnings; use File::Find; # Return a list of all symlinks found under $dir. sub SymLinkFind { my $dir = shift; my @SymLinks; find sub { -l and push @SymLinks, $File::Find::name }, $dir; return @SymLinks; } my @symlinks = SymLinkFind("."); print "Found ", scalar(@symlinks), " Symbolic Links...\n"; print "$_\n" for @symlinks;

Re: Strictly nested sub warnings
by GrandFather (Cardinal) on Oct 05, 2010 at 22:37 UTC

    Unrelated (directly) to your question, but in keeping with your desire to update your coding practises:

    1. Don't use subroutine prototypes. They probably don't do what you think.
    2. If you don't use prototypes you don't need to forward declare subs.
    3. Don't call subs using &subName. It probably doesn't do what you think either.
    4. Don't nest named subs, but you know that now.
    5. Generally use string interpolation instead of concatenation. For example: print "Found $params{numoLinks} Symbolic Links...\n";
    6. Initialise variables when you declare them. For example: my %params = (recurseDir => '.');
    True laziness is hard work

      Out of curiousity, why #5? I don't question that it's the case (you know better than me by job lots), I just wondered what the reason was.

      for(split(" ","tsuJ rehtonA lreP rekcaH")){print reverse . " "}print "\b.\n";
        Especially if you're joining lots of vars with short strings, concatenation can leave you drowning in symbols.

        It's fairly minor, but using concatenation when you could use interpolation instead tends to obscure the result you are aiming for. Compare the following two print statements:

        use strict; use warnings; my $tsuJ = 'tsuJ'; my $rehtonA = 'rehtonA'; my $lreP = 'lreP'; my $rekcaH = "\nrekcaH"; print join ' ', map {scalar reverse} split / /, $tsuJ . ' ' . $rehtonA + . ' ' . $lreP . ' ' . $rekcaH; print join ' ', map {scalar reverse} split / /, "$tsuJ $rehtonA $lreP +$rekcaH";
        True laziness is hard work

      It's probably a stupid question, but why not use prototypes?

      I like that I get some argument checking (not a whole lot, but that's actually better most of the time) by declaring named subs (i.e.  sub mySubroutine ($$) has two expected scalar args) with args, but I also like to stuff my subs at the bottom of my file, and so perl issues a warning if I don't put the prototype at the beginning of the code.

      Are you suggesting that to become more Perly, one must abandon naming arguments altogether in subs?

      Thanks again, all, for the suggestions. --Ray

Re: Strictly nested sub warnings
by kcott (Abbot) on Oct 07, 2010 at 06:55 UTC

    Here's another piece of documentation that seems pertinent. In perlref there's a section entitled Function Templates (almost at the bottom of the page).

    The part probably of most interest starts with:

    ... incurs mysterious warnings about "will not stay shared" ...

    and ends with

    This has the interesting effect of creating a function local to another function, something not normally supported in Perl.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (8)
As of 2014-11-27 13:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My preferred Perl binaries come from:














    Results (184 votes), past polls