Reputation: 20280
If I were to have a simple tied scalar class that increments every time it is read I could do that like this:
package Counter;
use strict;
use warnings;
sub TIESCALAR {
my $class = shift;
my $value = 0;
bless \$value, $class;
return \$value;
}
sub FETCH {
my $self = shift;
my $value = $$self;
$$self++;
return $value;
}
sub STORE {
my $self = shift;
$$self = shift;
}
1;
However to create a counter variable I have to use tie
. I could create one counter and export it. But what I really want to do is make it look OO. It seems that I could create a new
method like this:
sub new {
my $class = shift;
my $counter;
tie $counter, $class;
return $counter;
}
then in my main script get two counters by doing:
my $counter1 = Counter->new();
my $counter2 = Counter->new();
I am assuming this doesn't work because a tie doesn't survive a copy (I read that in the docs somewhere), is there simply no way to do this?
NB. I know it is only a matter of style, but it would LOOK more correct to the eye.
Upvotes: 2
Views: 129
Reputation: 40152
Tie magic is not carried across assignment because it applies to the variable itself, not the value it contains. You have a few options:
Returning a reference:
sub new {tie my $ret, ...; \$ret}
my $counter = Counter->new;
say $$counter;
Assigning to a glob:
our ($counter);
*counter = Counter->new; # same new as above
say $counter;
Or you could pass the variable into the constructor:
sub new {my $class = shift; tie $_[0], $class}
Counter->new(my $counter);
say $counter;
You can even make a constructor that works with both methods:
sub new {
my $class = shift;
tie $_[0] => $class;
\$_[0]
}
our $glob; *glob = Counter->new;
Counter->new(my $lexical);
In the last two examples, tie
is passed $_[0]
directly. The reason for this is that the elements of @_
are aliases to the argument list, so it works as if you had typed the my $counter
in the tie
line.
And finally, while your example is very clear and follows best practices, in the spirit of TIMTOWTDI, you could write your entire class like this:
{package Counter;
sub TIESCALAR {bless [0]}
sub FETCH {$_[0][0]++}
sub STORE {$_[0][0] = $_[1]}
sub new {tie $_[1] => $_[0]; \$_[1]}
}
One last thing to mention. While your question is about tied variables, you can also use overloading to achieve this:
{package Counter;
use overload fallback => 1, '""' => sub {$_[0][0]++};
sub new {bless [0]}
}
my $counter = Counter->new; # overloading survives the assignment
say $counter;
But you loose the ability to reset the counter via assignment. You could add a sub set {$_[0][0] = $_[1]}
method to Counter
.
Upvotes: 11