Joel Berger
Joel Berger

Reputation: 20280

Constructor for tied scalar

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

Answers (1)

Eric Strom
Eric Strom

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

Related Questions