Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Making a variable in a sub retain its value between calls

by crashtest (Curate)
on Apr 18, 2005 at 20:27 UTC ( [id://449011] : perlquestion . print w/replies, xml ) Need Help??

crashtest has asked for the wisdom of the Perl Monks concerning the following question:

Wise Monks,

This morning I was writing a subroutine that needed to keep track of some simple data between calls. Because the data in question was really local to the subroutine, I didn't want to pass a reference to it on each call, especially since this reference would have had to go through a number of other enclosing functions first. I wanted a variable that was limited to the subroutine's scope, but would retain its value between subroutine calls. I can't help but suspect this need stemmed from some poor program design somewhere, but there I was, and that was what I needed.

I realized (with horror) that I was looking for something similar to VisualBasic's Static Modifier. Luckily I left VB and other MS technology behind me some time ago, so I just did something similar to the following:
use strict; use warnings; test() for (1..3); { my $memory = 0; sub test{ print "Value of static var is ", $memory++, "\n"; } } __END__ Value of static var is 0 Value of static var is 1 Value of static var is 2
I suppose this works well enough, and it seems fairly clean to me. But I was curious if there was some other way to get this behavior, and what Real Perl Programmers do in these cases. Is there a clever way to accomplish what I want without the enclosing block? Some trickery to be found on CPAN, perhaps?

Thanks in advance for your input. Please don't flame me too hard for having used VB!

Replies are listed 'Best First'.
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.

      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.

      the lowliest monk

        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!

      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.
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.
      Yep. And I would see the possibilities (e.g. sharing static var's for a subset of functions) as a win.

      Ordinary morality is for ordinary people. -- Aleister Crowley
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


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.
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.
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
      It is documented. It is documented as being undefined behaviour, which is even less reliable than undocumented.
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. :)

      Lately i could argue its written in Martian Genius C and not the normal plain C that most of us know.

      ---
      demerphq

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

      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.

        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