How do I pass a variable through multiple subroutines in perl? - perl

I have a variable currentUser in a sub routine. It carries through to one subroutine, but does not carry through to another subroutine. How can I pass a variable through multiple subroutines while keeping the value?
sub login {
&app_header;
print <<EOF;
<form name="macform" method="POST" enctype="application/x-www-form-urlencoded" action="$fullurl">
...stuff
EOF
}
sub html_menu {
$me = $currentUser;
print $me;
print <<EOF;
<form name="menuform" method="POST" enctype="application/x-www-form-urlencoded" action="$fullurl">
..stuff
EOF
&app_list_button;
print "<br>";
&app_search_button;
print "<br>";
&app_edit_button;
print "</div>";
}
When I attempt to do the same thing the html_form sub does with currentUser in a new sub called after html_form), the variable does not display as what the user entered during login.

sub first {
my $currentUser = shift;
second($currentUser);
}
sub second {
my $currentUser = shift;
third($currentUser);
}
...and so on.
Passing a variable as argument is done through the #_ variable. You should not attempt to use global variables, it is not a good way to do it. You can do it like so:
my ($arg1, $arg2, #rest) = #_;
Or by using shift, pop and the other methods of manipulating arrays, like I above used shift.
If you are not already doing so, I recommend very strongly that you use
use strict;
use warnings;
It will help you solve many simple problems.

The safest way is to use the variable as an argument:
sub r1 {
my $arg = shift;
r2($arg);
}
sub r2 {
my $arg = shift;
print "$arg in r2\n";
}

When I attempt to do the same thing the html_form sub does with currentUser in a new sub called after html_form), the variable does not display as what the user entered during login.
Firstly, this is a rather complex example. You should have tried to break it down. To add to what others have said thus far, I'm going to tell you the & prefix on a subroutine's name without adding () changes the meaning. You use this in your examples, and I'm not sure if you know what it does. All of these have slightly different meanings.
foo() This simply calls sub foo with no arguments.
&foo This calls foo passing in #_ implicitly. Important note, if foo modifies #_ by reference then the #_ will change for the callee too.
&foo() This is just a relic of perl4. This calls foo with the explicit subref sigil. This is deprecated in all contexts.
there are also minor differences with prototypes and the & sigil that are outside of the bounds of the question
For more information see perldoc -q "calling a function" and perldoc perlsub

Related

Getting Variable "#xml_files" will not stay shared at ... line

I have the following Perl code:
sub merge_xml {
foreach my $repository ('repo1', 'repo2') {
my #xml_files;
sub match_xml {
my $filename = $File::Find::dir . '/' . $_;
if ($filename =~ m%/(main|test)\.xml$%) {
push(#xml_files, $filename);
}
}
find(\&match_xml, $repository);
print Dumper(\#xml_files);
}
}
And I am getting the warning:
Variable "#xml_files" will not stay shared at ./script/src/repair.pl line 87.
How does one fix this?
PS find as in File::Find
"Nested" named subs in fact aren't -- they're compiled as separate subroutines, and so having them written as "nested" can only be misleading.
Further, this creates a problem since the "inner" subroutine supposedly closes over the variable #xml_files that it uses, which gets redefined on each new call, being lexical. But the sub, built at compile-time and not being a lexical closure, only keeps the refernce to the value at first call and so it works right only upon the first call to the outer sub (merge_xml here).
We do get the warning though. With use diagnostics; (or see it in perldiag)
Variable "$x" will not stay shared at -e line 1 (#1)
(W closure) An inner (nested) named subroutine is referencing a
lexical variable defined in an outer named subroutine.
When the inner subroutine is called, it will see the value of
the outer subroutine's variable as it was before and during the first
call to the outer subroutine; in this case, after the first call to the
outer subroutine is complete, the inner and outer subroutines will no
longer share a common value for the variable. In other words, the
variable will no longer be shared.
This problem can usually be solved by making the inner subroutine
anonymous, using the sub {} syntax. When inner anonymous subs that
reference variables in outer subroutines are created, they
are automatically rebound to the current values of such variables.
So pull out that "inner" sub (match_xml) and use it normally from the "outer" one (merge_xml). In general you'd pass the reference to the array (#xml_files) to it; or, since in this case one cannot pass to File::Find's find, can have the array in such scope so to be seen as needed.
Or, since the purpose of match_xml is to be find's "wanted" function, can use an anonymous sub for that purpose so there is no need for a separate named sub
find( sub { ... }, #dirs );
Or store that coderef in a variable, as shown in Ed Heal's answer
my $wanted_coderef = sub { ... };
find( $wanted_coderef, #dirs );
With help from zdim I came up with:
sub merge_xml {
foreach my $repository ('repo1', 'repo2') {
my #xml_files;
my match_xml = sub {
my $filename = $File::Find::dir . '/' . $_;
if ($filename =~ m%/(main|test)\.xml$%) {
push(#xml_files, $filename);
}
};
find($match_xml, $repository);
print Dumper(\#xml_files);
}
}
Might I suggest another alternative. By using a factory function, you can eliminate the need to hand write a find subroutine each time.
A factory is a function that generates another function (or subroutine in this case). You feed it some parameters and it creates a custom subroutine with those parameters bolted in. My example uses a closure but you could also build it with a string eval if the closure is costly for some reason.
sub match_factory {
my ($filespec, $array) = #_;
# Validate arguments
die qq{File spec must be a regex, not "$filespec"\n}
unless ref $filespec eq "Regexp";
die qq{Second argument must be an array reference, not "$array"\n}
unless ref $array eq "ARRAY";
# Generate an anonymous sub to perform the match check
# that creates a closure on $filespec and $array
my $matcher = sub {
# Automatically compare against $_
m/$filespec/ and
push(#$array, $File::Find::name);
};
return $matcher;
}
sub merge_xml {
my #repos = #_ # or qw/foo bar .../;
foreach my $repository (#repos) {
my #matched_files;
find(
match_factory( qr/\/(main|test)\.xml$/, \#matched_files ),
$repository
);
print Dumper(\#matched_files);
}
}
HTH

can a variable be defined in perl whose value cannot be changed in a subroutine?

In the following script, I declare and modify #basearray in the main program. Inside the dosomething subroutine, I access #basearray, assign it to an array local to the script, and modify the local copy. Because I have been careful to change the value only of local variables inside the subroutine, #basearray is not changed.
If I had made the mistake of assigning a value to #basearray inside the subroutine, though, it would have been changed and that value would have persisted after the call to the subroutine.
This is demonstrated in the 2nd subroutine, doagain.
Also, doagain receives the reference \#basearray as an argument rather than accessing #basearray directly. But going to that additional trouble provides no additional safety. Why do it that way at all?
Is there a way to guarantee that I cannot inadvertently change #basearray inside any subroutine? Any kind of hard safety device that I can build into my code, analogous to use strict;, some combination perhaps of my and local?
Am I correct in thinking that the answer is No, and that the only solution is to not make careless programmer errors?
#!/usr/bin/perl
use strict; use warnings;
my #basearray = qw / amoeba /;
my $count;
{
print "\#basearray==\n";
$count = 0;
foreach my $el (#basearray) { $count++; print "$count:\t$el\n" };
}
sub dosomething
{
my $sb_name = (caller(0))[3];
print "entered $sb_name\n";
my #sb_array=( #basearray , 'dog' );
{
print "\#sb_array==\n";
$count = 0;
foreach my $el (#sb_array) { $count++; print "$count:\t$el\n" };
}
print "return from $sb_name\n";
}
dosomething();
#basearray = ( #basearray, 'rats' );
{
print "\#basearray==\n";
$count = 0;
foreach my $el (#basearray) { $count++; print "$count:\t$el\n" };
}
sub doagain
{
my $sb_name = (caller(0))[3];
print "entered $sb_name\n";
my $sf_array=$_[0];
my #sb_array=#$sf_array;
#sb_array=( #sb_array, "piglets ... influenza" );
{
print "\#sb_array==\n";
$count = 0;
foreach my $el (#sb_array) { $count++; print "$count:\t$el\n" };
}
print "now we demonstrate that passing an array as an argument to a subroutine does not protect it from being globally changed by programmer error\n";
#basearray = ( #sb_array );
print "return from $sb_name\n";
}
doagain( \#basearray );
{
print "\#basearray==\n";
$count = 0;
foreach my $el (#basearray) { $count++; print "$count:\t$el\n" };
}
There isn't a pragma or a keyword or such, but there are well established "good practices," which in this case completely resolve what you reasonably ponder about.
The first sub, dosomething, commits the sin of using variables visible in its scope but defined in the higher scope. Instead, always pass needed data to a subroutine (exceptions are rare, in crystal clear cases).
Directly using data from "outside" defies the idea of a function as an encapsulated procedure, exchanging data with its users via a well defined and clear interface. It entangles ("couples") sections of code that are in principle completely unrelated. In practice, it can also be outright dangerous.
Also, the fact the #basearray is up for grabs in the sub is best considered an accident -- what when that sub gets moved to a module? Or another sub is introduced to consolidate code where #basearray is defined?
The second sub, doagain, nicely takes a reference to that array. Then, to protect the data in the caller, one can copy the caller's array to another one which is local to the sub
sub doagain {
my ($ref_basearray) = #_;
my #local_ba = #$ref_basearray;
# work with local_ba and the caller's basearray is safe
}
The names of local lexical variables are of course arbitrary, but a convention where they resemble the caller's data names may be useful.
Then you can adopt a general practice, for safety, to always copy input variables to local ones. Work directly with references that are passed in only when you want to change the caller's data (relatively rare in Perl). This may hurt efficiency if it's done a lot with sizeable data, or when really large data structures are involved. So perhaps then make an exception and change data via its reference, and be extra careful.
(Putting my comment as answer)
One way to guarantee not changing a variable inside a subroutine is to not change it. Use only lexically scoped variables inside the subroutine, and pass whatever values you need inside the subroutine as arguments to the subroutine. It is a common enough coding practice, encapsulation.
One idea that you can use -- mainly as practice, I would say -- to force yourself to use encapsulation, is to put a block around your "main" code, and place subroutines outside of it. That way, if you should accidentally refer to a (formerly) global variable, use strict will be able to do it's job and produce a fatal error. Before runtime.
use strict;
use warnings;
main: { # lexical scope reduced to this block
my #basearray = qw / amoeba /;
print foo(#basearray); # works
print bar(); # fatal error
} # END OF MAIN lexical scope of #basearray ends here
sub foo {
my #basearray = #_; # encapsulated
return $basearray[1]++;
}
sub bar {
return $basearray[1]++; # out of scope ERROR
}
This will not compile, and will produce the error:
Global symbol "#basearray" requires explicit package name at foo.pl line 15.
Execution of foo.pl aborted due to compilation errors.
I would consider this a training device to force yourself to using good coding practices, and not something to necessarily use in production code.
There are several solutions with various levels of pithiness from "just don't change it" to "use an object or tied array and lock down the update functions". An intermediate solution, not unlike using an object with a getter method, is to define a function that returns your array but can only operate as an rvalue, and to use that function inside subroutines.
my #basearray = (...);
sub basearray { return #basearray }
sub foo {
foreach my $elem (basearray()) {
...
}
#bar = map { $_ *= 2 } basearray(); # ok
#bar = map { $_ *= 2 } #basearray; # modifies #basearray!
}
TLDR: yes, but.
I'll start with the "but". But it's better to design your code so that the variable simply doesn't exist in the scope where the untrusted function is defined.
sub untrusted_function {
...
}
my #basearray = qw( ... ); # declared after untrusted_function
If untrusted_function needs to be able to access the contents of the array, pass it a copy of the array as a parameter, so it can't modify the original.
Now here's the "yes".
You can mark the array as read-only before calling the untrusted function.
Internals::SvREADONLY($_, 1) for #basearray;
Internals::SvREADONLY(#basearray, 1);
Then mark it read-write again after the function has finished.
Internals::SvREADONLY(#basearray, 0);
Internals::SvREADONLY($_, 0) for #basearray;
Using Internals::SvREADONLY(#basearray, $bool) modifies the read-only state of the array itself, preventing elements from being added or removed from it; Internals::SvREADONLY($_, $bool) for #basearray modifies the read-only state of each element in the array too, which you probably want.
Of course, if your array contains references like blessed objects, you then need to consider whether you need to recurse into the references, marking them read-only too. (But can also be a concern with the shallow copy of the array I mentioned in the preferred solution!)
So yes, it is possible to prevent a sub from accidentally modifying a variable by marking that variable read-only before calling the sub, but it's a better idea to restructure your code so the sub simply doesn't have access to the variable at all.
Yes, but.
Here is a prototype that uses #TLP's answer.
#!/usr/bin/perl
use strict; use warnings;
{ # block_main BEG
my #basearray = qw / amoeba elephants sequoia /;
print join ( ' ', 'in main, #basearray==', join ( ' ', #basearray ), "\n" );
print "Now we call subroutine to print it:\n"; enumerateprintarray ( \#basearray );
my $ref_basearray = changearray ( \#basearray, 'wolves or coyotes . . . ' );
#basearray = #$ref_basearray;
print "Now we call subroutine to print it:\n"; enumerateprintarray ( \#basearray );
} # block_main END
sub enumerateprintarray
{
my $sb_name = (caller(0))[3];
#print join ( '' , #basearray ); # mortal sin! for in the day that thou eatest thereof thou shalt surely die.
my $sb_exact_count_arg = 1;
die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar #_ ) == $sb_exact_count_arg );
my $sf_array = $_[0];
my #sb_array = #$sf_array;
my $sb_count = 0;
foreach (#sb_array)
{
$sb_count++;
print "\t$sb_count:\t$_\n";
}
}
sub changearray
{
my $sb_name = (caller(0))[3];
#print join ( '' , #basearray ); # in the day that thou eatest thereof thou shalt surely die.
my $sb_exact_count_arg = 2;
die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar #_ ) == $sb_exact_count_arg );
my ( $sf_array, $addstring ) = #_;
my #sb_array = #$sf_array;
push #sb_array, $addstring;
return ( \#sb_array );
}

is it allowed to pass pipes to constructors?

I tried to do something very fancy in Perl, and I think I'm suffering the consequences. I don't know if what I was trying to do is possible, actually.
My main program creates a pipe like this:
pipe(my $pipe_reader, my $pipe_writer);
(originally it was pipe(PIPE_READER, PIPE_WRITER) but I changed to regular variables when I was trying to debug this)
Then it forks, but I think that is probably irrelevant here. The child does this:
my $response = Response->new($pipe_writer);
The constructor of Response is bare bones:
sub new {
my $class = shift;
my $writer = shift;
my $self = {
writer => $writer
};
bless($self, $class);
return($self);
}
Then later the child will write its response:
$response->respond(123, "Here is my response");
The code for respond is as follows:
sub respond {
my $self = shift;
my $number = shift;
my $text = shift;
print $self->{writer} "$number\n";
print $self->{writer} "$text\n";
close $self->{writer}
}
This triggers a strange compile error: 'String found where operator expected ... Missing operator before "$number\n"?' at the point of the first print. Of course this is the normal syntax for a print, except that I have the object property instead of a normal handle AND it happens to be a pipe, not a file handle. So now I'm wondering if I'm not allowed to do this.
From print
If you're storing handles in an array or hash, or in general whenever you're using any expression more complex than a bareword handle or a plain, unsubscripted scalar variable to retrieve it, you will have to use a block returning the filehandle value instead, ...
print { $files[$i] } "stuff\n";
print { $OK ? *STDOUT : *STDERR } "stuff\n";
(my emphasis)
So you need
print { $self->{writer} } "$number\n";
Or, per Borodin's comment
$self->{writer}->print("$number\n");
The syntax of print is special, see for example this post and this post. For one, after print must come either a "simple" filehandle or a block evaluating to one, as quoted above, to satisfy the parser.
But with the dereference (arrow) operator the filehandle is found to be an IO::File object† and so its parent's IO::Handle::print method is invoked on it.
Prior to v5.14 there had to be use IO::Handle; for this to work, though not anymore. See this post and links in it for more.
Note that print FILEHANDLE LIST is not an indirect method call,
even as it may appear to be. It is just a function call to the print builtin under rather special syntax rules. It is only with an explicit ->
that an IO::Handle method gets called.
† It is either blessed into the class as the method call is encountered (and fails), or at creation; I can't find it in docs or otherwise resolve whether filehandles are blessed at creation or on demand
perl -MScalar::Util=blessed -wE'
pipe(RD,WR);
say *WR{IO}; #--> IO::File=IO(0xe8cb58)
say blessed(WR)//"undef"; #--> undef
'
(warns of unused RD)   We can't do this with lexical filehandles as they are not in the symbol table.
But once needed a filehandle is an IO::File or IO::Handle object (depending on Perl version).

What happens if you call shift inside an anonymous sub?

First off, apologies if this question is ill-posed; I don't actually know a heck of a lot of perl.
I'm trying to debug some existing code that is supposed to send grades from our online homework system called WeBWorK to an LMS. I'm running into a weird error where I think something isn't getting initialized right, or perhaps isn't the right class. I suspect that the problem might be here:
sub go {
my $self = shift;
my $r = $self->r;
my $ce = $r->ce;
# If grades are begin passed back to the lti then we peroidically
# update all of the grades because things can get out of sync if
# instructors add or modify sets.
if ($ce->{LTIGradeMode}) {
my $grader = WeBWorK::Authen::LTIAdvanced::SubmitGrade->new($r);
my $post_connection_action = sub {
my $grader = shift;
# catch exceptions generated during the sending process
my $result_message = eval { $grader->mass_update() };
if ($#) {
# add the die message to the result message
$result_message .= "An error occurred while trying to update grades via LTI.\n"
. "The error message is:\n\n$#\n\n";
# and also write it to the apache log
$r->log->error("An error occurred while trying to update grades via LTI: $#\n");
}
};
if (MP2) {
$r->connection->pool->cleanup_register($post_connection_action, $grader);
} else {
$r->post_connection($post_connection_action, $grader);
}
}
... # a bunch of other stuff happens in the "go" sub
I kinda suspect that the issue is with the $grader variable; in particular, I don't know what my $grader = shift; does inside an anonymous sub. Like, if the sub had a name, it would be more clear that shift is giving the first argument passed to the sub. But since it's anonymous, I don't know what it thinks its arguments are.
Further, I'm not really sure why that line is needed at all. Like, from my googling, I'm given to understand that the point of an anonymous sub is to keep all the variables from the surrounding environment in scope. So why do we need to redefine $grader inside the anonymous sub in the first place?
Thanks for helping a perl noob out! :)
There's nothing special about anon subs in this regard.
my $cr = sub {
my $arg = shift;
say $arg;
};
$cr->("foo"); # Prints "foo"
$cr->("bar"); # Prints "bar"
In your case, you pass $post_connection_action and $grader to cleanup_register or post_connection with the expectation that it will result in a call to &$post_connection_action with $grader as its first argument. Whether the expectation is correct or not depends on the implementation of cleanup_register and post_connection, of which I know nothing.
Note that another solution presents itself here. Subs have access to the lexicals that were in scope when the sub operator was evaluated.
my $prefix = "> ";
my $cr = sub {
my $arg = shift;
say "$prefix$arg"; # Captures $prefix from sub{} scope.
};
$cr->("foo"); # Prints "> foo"
The above is true even if captured lexicals would otherwise no longer exist by the time the sub is called.
my $cr;
{
my $prefix = "> ";
$cr = sub {
my $arg = shift;
say "$prefix$arg"; # Captures $prefix from sub{} scope.
};
} # $prefix would normally stop existing here.
$cr->("foo"); # Prints "> foo"
That means you don't need to pass $grader as an argument. It can simply be captured. Just leave out my $grader = shift; (and don't pass $grader to
cleanup_register or post_connection).

Why can't my Perl subroutine see the value for the variable in the foreach loop that called it?

I hope this is something straightforward that I'm doing wrong. I saw something online about "variable suicide" that looked good, but it was for an older version and I'm on 5.10.1.
Anyway - a variable that I declared - $RootDirectory - just suddenly loses its value, and I can't figure out why.
Here's a script to reproduce the problem. When I run through the script in debug mode (perl -d) I can get it to print out the $RootDirectory at line 21 and 26. But it's gone by line 30.
use strict;
my $RootDirectory;
my #RootDirectories;
#RootDirectories = (
'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\'
,'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\'
,'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\'
);
foreach $RootDirectory (#RootDirectories) {
# $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
# print ' In foreach ' . $RootDirectory. "\n";
RunSchema ();
}
exit(0);
sub RunSchema() {
# print ' In RunSchema ' . $RootDirectory. "\n";
CreateTables ();
}
sub CreateTables() {
# print ' In CreateTables ' . $RootDirectory. "\n";
SQLExecFolder ('tbl');
}
sub SQLExecFolder() {
print ' In SQLExecFolder ' . $RootDirectory. "\n"; # Variable $RootDirectory value is gone by now
}
EDIT
Thanks for all the comments! I think for now I'll use the "our" keyword which appears to work well - thanks Nathan. Also thanks toolic about the Use Warnings - I think I'm sold on that one!
The thing that continues to confuse me is why, when I did debug mode (perl -d), and stepped through the code, doing "p $RootDirectory" I got the expected output at lines 21 and 26, but not line 30. How is the situation different at line 30?
Also, I appreciate the comments about best practice being to pass $RootDirectory as a function parameter. I wanted to avoid that because I have so many functions following that - i.e. RunSchema calls CreateTables which calls SQLExecFolder. All of them would have to have the same parameter passed. Does it still make sense in this case, or are there any better ways to structure this?
What Nathan said is correct. That aside, why don't you pass in the value? It's better practice anyway:
foreach $RootDirectory (#RootDirectories) {
# $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
# print ' In foreach ' . $RootDirectory. "\n";
RunSchema ($RootDirectory);
}
sub SQLExecFolder {
my $RootDirectory = shift;
print ' In SQLExecFolder ' . $RootDirectory. "\n";
}
You're declaring $RootDirectory as the loop variable in a foreach loop. As far as I understand, that means that its value is localized to the loop, and its value is restored to its previous value at the end of the loop.
In your case the variable was never assigned, so at the end of the loop it returns to its previous value of undef.
Edit: Actually, the problem is that $RootDirectory is declared with my, so it is undefined in other scopes. In the functions RunSchema, CreateTables and SQLExecFolder the variable is undefined, regardless of the localization of the foreach.
If you want the variable to be declared for strictness, but want it to be global, declare $RootDirectory with our:
our $RootDirectory;
Edit: That being said, it's not always a good idea to use a global variable. You're better off passing the variable as a parameter to the functions as others have suggested.
Others have answered your question correctly. I just want to emphasize that you should add use warnings; to your code. It would have given a clue to your problem, and it would alert you to another potential hazard.
foreach variable is special - it's local to the loop.
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 localisation
occurs only in a foreach loop.
Please take a look here
The iterator variable in foreach loop is always localized to the loop. See the foreach section in perlsyn. You can pass it to a subroutine as a parameter.
RE: When to use a global variable?
Global variables are risky because they can be changed at any time by any part of the code that accesses them. In addition, it is difficult to track when and where a change occurs, which makes it harder to track down unintentional consequences from modification. In short, each global variable increases coupling between the subroutines that use it.
When does it make sense to use a global? When the benefits outweigh the risks.
If you have many different values needed by most or all of your subroutines, it seems like a good time to use global variables. You can simplify every subroutine invocation, and make the code clearer, right?
WRONG. In this case the right approach is to aggregate all those distinct variables in one container data structure. So instead of foo( $frob, $grizzle, $cheese, $omg, $wtf ); you have foo( $state, $frob ); Where $state = { grizzle => $grizzle, cheese => $cheese, omg => $omg, wtf => $wtf };.
So now we have one variable to pass around. All those sub calls are much simpler. Yet, even so, this is onerous and you still want to clean up the extra argument from each routine.
At this point you have several options:
Make $state global and just access it directly.
Make $state into a configuration object and use methods to control access to attributes.
Make the whole module into a class, and store all the state information in an object.
Option 1 is acceptable for small scripts with few routines. The risk of hard to debug errors is small.
Option 2 makes sense when there is no obvious relationship between the different routines in the module. Using a global state object helps because it decreases coupling between code that accesses it. It is also easier to add logging to track changes to the global data.
Option 3 works well if you have a group of closely related functions that operate on the same data.
Your sample code seems like a good candidate for option 3. I created a class called MySchema and all the methods that operate on a specific directory are now methods. The invoking object carries the data it needs with it.
Now we have nice, clean code and no globals.
use strict;
use warnings;
my #directories = (
'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\',
'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\',
'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\',
);
for my $schema ( make_schemata(#directories) ) {
$schema->run;
}
sub make_schemata {
my #schemata = map { MySchema->new( directory => $_ } #_;
return #schemata;
}
BEGIN {
package MySchema;
use Moose;
has 'directory' => (
is => 'ro',
isa => 'Str',
required => 1,
);
sub run {
my $self = shift;
$self->create_tables;
}
sub create_tables {
my $self = shift;
$self->sql_exec_folder('tbl');
}
sub sql_exec_folder {
my $self = shift;
my $dir = $self->directory;
print "In SQLExecFolder $dir\n";
}
1;
}
As a bonus, the code in the BEGIN block can be removed and placed in a separate file for reuse by another script. All it needs to be a full-fledged module is its own file named MySchema.pm.
Not a bad effort. Here are a couple of small improvements, and one "fix" which is to pass the variable to the subroutines, as a function parameter because the $RootDirectory variable is scoped (i.e. restricted) to within the foreach loop. In general it is also considered good practice in order to make explicit what variables are being passed and/or accessed by various subroutines.
use strict;
use warnings;
sub RunSchema() {
my $root_dir = shift;
CreateTables($root_dir);
}
sub CreateTables() {
my $root_dir = shift;
SQLExecFolder('tbl', $root_dir);
}
sub SQLExecFolder() {
my ($name, $root_dir) = #_;
}
######################################################
my #RootDirectories = qw(
c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\
c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\
c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\
);
foreach my $RootDirectory (#RootDirectories) {
# print ' In foreach ' . $RootDirectory. "\n";
RunSchema($RootDirectory);
}
exit(0);