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
Re: Strictly nested sub warnings
by JavaFan (Canon) on Oct 05, 2010 at 13:09 UTC
|
| [reply] |
|
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 | [reply] [d/l] |
|
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.) | [reply] [d/l] |
|
|
| [reply] |
|
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;
| [reply] [d/l] [select] |
|
|
|
| [reply] [d/l] |
|
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];?
| [reply] [d/l] |
Re: Strictly nested sub warnings
by GrandFather (Saint) on Oct 05, 2010 at 22:37 UTC
|
Unrelated (directly) to your question, but in keeping with your desire to update your coding practises:
- Don't use subroutine prototypes. They probably don't do what you think.
- If you don't use prototypes you don't need to forward declare subs.
- Don't call subs using &subName. It probably doesn't do what you think either.
- Don't nest named subs, but you know that now.
- Generally use string interpolation instead of concatenation. For example: print "Found $params{numoLinks} Symbolic Links...\n";
- Initialise variables when you declare them. For example: my %params = (recurseDir => '.');
True laziness is hard work
| [reply] [d/l] [select] |
|
| [reply] [d/l] |
|
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
| [reply] [d/l] |
|
Especially if you're joining lots of vars with short strings, concatenation can leave you drowning in symbols.
| [reply] |
|
|
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
| [reply] [d/l] |
|
| [reply] |
|
Re: Strictly nested sub warnings
by kcott (Archbishop) 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.
| [reply] |
|
|