Search code examples
perlconstructortie

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.


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.