Re: Making a variable in a sub retain its value between calls
by Roy Johnson (Monsignor) on Apr 18, 2005 at 20:42 UTC
|
That's the way to do it in Perl, but one note of caution: your initializer isn't being executed before your loop. The declaration and sub definition happen at compile time, but the assignment is a runtime thing, and the code gets run from top to bottom. Your output is unchanged even if you initialize $memory to 'a' (which the autoincrement op should then change to b and c in turn).
You can make your enclosing block a BEGIN block, or you can put it above the rest of your code.
Caution: Contents may have been coded under pressure.
| [reply] |
|
Update: In the days and weeks after the debate that followed this comment of mine, through reading and CB conversations I have convinced myself that INIT blocks are not "up to spec", so I'm avoiding them. See in particular TimToady's node in this thread.
In the TIMTOWTDI vein, I'm partial to INIT blocks myself:
use strict;
use warnings;
test() for (1..3);
{
my $memory;
INIT { $memory = 'a' }
sub test {
print "Value of static var is ", $memory++, "\n";
}
}
__END__
Value of static var is a
Value of static var is b
Value of static var is c
I like using an extra internal INIT block only because it clearly sets the initialization code off from the rest.
Of course, if one doesn't like INIT or BEGIN blocks, one can always explicitly check in test that $memory has been initialized and remedy the situation if necessary, but that's ugly, IMO.
| [reply] [d/l] |
|
I'm partial to INIT blocks myself
Noooooooooooo! Please please please don't do that! It will break if you put the code using the INIT block in a module and that modules is directly or indirectly required (i.e., loaded after program compile-time). INITs are not executed right after the file in which it was found is done compiling. It's executed after the program file is done compiling. INIT is used when you need to do something delay execution of something to before program run-time.
Personally I go with adding a BEGIN before the bare block, thus avoiding to retype the variable name.
BEGIN {
my $memory = 'a';
sub test {
print "Value of static var is ", $memory++, "\n";
}
}
Update: added clarification.
ihb
See perltoc if you don't know which perldoc to read!
| [reply] [d/l] [select] |
|
|
|
|
|
Thanks for the catch! I'm not initializing the variable in my actual code, and only set $memory = 0 in my post on a whim. But it illustrated a subtlety I would have probably missed on my own. And your comment seems to have sparked a lively and interesting debate on BEGIN vs. INIT blocks.
| [reply] [d/l] |
Re: Making a variable in a sub retain its value between calls
by ikegami (Patriarch) on Apr 18, 2005 at 20:31 UTC
|
There's no special modifier in Perl, so doing a closure as you are doing is the best way. $memory can only be accessed by test(), so it's identical to static vars in C and (I presume) in VB.
| [reply] |
|
| [reply] |
Re: Making a variable in a sub retain its value between calls
by sh1tn (Priest) on Apr 18, 2005 at 20:48 UTC
|
I suppose you mean closure (perldoc -q closure).
use strict;
use warnings;
my $test = test();
$test->() for (1..3);
$test->() for (1..3);
sub test{
my $var = 1;
return sub {
print("private state is ", $var++, "\n")
};
}
__STDOUT__
private state is 1
private state is 2
private state is 3
private state is 4
private state is 5
private state is 6
| [reply] [d/l] |
Re: Making a variable in a sub retain its value between calls
by perrin (Chancellor) on Apr 18, 2005 at 20:52 UTC
|
The other way to do this, if you don't want to make that data global, is to use objects. Objects are a good way to package up some behaviors (subroutines) with some data. | [reply] |
Re: Making a variable in a sub retain its value between calls
by BigRare (Pilgrim) on Apr 18, 2005 at 20:53 UTC
|
You might want to take a look at This Node
What I generally do if I need to recycle a variable in a sub routine is just call the variable at the beginning of the script.
#!/usr/bin/perl
use warnings;
use strict;
my $counter = 0;
&mySub;
&mySub;
&mySub;
sub mySub {
print "My Value is ", $counter++, "\n";
}
Since $counter exists before the sub is called, the sub inherits the variable and updating it should work fine. | [reply] [d/l] |
Re: Making a variable in a sub retain its value between calls
by dragonchild (Archbishop) on Apr 19, 2005 at 00:51 UTC
|
Note: this relies on undocumented behavior. The behavior it depends on may be changed without warning.
sub foo {
my $x if 0;
print "X is ", $x++, $/;
}
foo() for 3 .. 5;
----
X is 0
X is 1
X is 2
| [reply] [d/l] |
|
It is documented. It is documented as being undefined behaviour, which is even less reliable than undocumented.
| [reply] |
Re: Making a variable in a sub retain its value between calls
by sgifford (Prior) on Apr 18, 2005 at 22:01 UTC
|
There's no shame in knowing VB's Static Modifier. C has the same thing, with the same name even, and Perl is written in C. :)
| [reply] |
|
| [reply] |
Re: Making a variable in a sub retain its value between calls
by mattr (Curate) on Apr 19, 2005 at 15:45 UTC
|
Hi,
As everyone says, that closure will do the trick. But I cringed since I had trained myself to say whoa there! in such cases, since persistence of a lexical is also a nasty bug if you don't mean it (and perl documentation, some at least, says as much). I think an object is safer, here's one possibility. Also it is easier to maintain.
I'm letting you access the raw value of the counter through hash syntax so you don't need to use Exporter. You could also put everything from package StaticCtr to just before package main into another file, StaticCtr.pm (end it with 1;). Then you can use StaticCtr; in any of your programs.
The new subroutine could be just a line really, it is longer so you can play with it more (see the perlobj manual).
#!/usr/bin/perl
package StaticCtr;
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
bless($self, $class);
$self->{ctr} = 0;
return $self;
}
sub inc {
my $self = shift;
$self->{ctr}++;
return $self->{ctr};
}
package main;
my $c = new StaticCtr;
for (1..6) {
print " incremented to: " . $c->inc . "\n";
}
print "Final value is " . $c->{ctr} . "\n";
This prints:
[mattr@taygeta perlmonks]$ ./ctr.pl
incremented to: 1
incremented to: 2
incremented to: 3
incremented to: 4
incremented to: 5
incremented to: 6
Final value is 6
| [reply] [d/l] [select] |
|
Unforuately, a reference to the object must be passed to the function, and the OP explicitely requested that the solution didn't do this. I do like passing a state object as you suggest, though. There's much less hidden mojo that way.
Sorry for this banal reply. The original version of it was completely wrong.
| [reply] |
|
Ah, my bad.
I was a little confused what he was getting at. Also was thinking "well you could use a global but that would not be so elegant.." etc. I guess there is a good time to use closures like above. Thanks!
Matt
| [reply] |