Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top