V
Veli-Pekka Tätilä
Hi,
I'd like to treat an object like an array but also provide methods for
accessing bits of the object state that aren't array-like. An example in
which this would be useful comes from Ruby, its regexp Match objects are
indexable like arrays for accessing back references, yet they also have
a nice interface via methods. Can Perl also do objects having both
methods and hash or array-like indexing?
I think I've found one possible solution, for arrays at least, after
reading Object Oriented Perl Ch 9.7 on tied objects. You could have a
constructor that returns a blessed ref to an underlying tied array,
which the user accesses like any array ref. As it is tied, its easy to
make the array read-only, for instance, if more restricted access is
desired. As the object made by the constructor is a blessed scalar the
user can also call methods on it. One way to handle the methods, and
also be able to store object state beside the array itself, is to
implement the tied array as a blessed hash behind the scenes. When a
method in which the non-array bits of the object are needed gets
called, you can dig up the underlying object behind the tied array via
tied.
To my surprise, at least in this simple script, both the array and the
object usage seem to be working OK. On second thought, putting the
methods dealing with tied variables in a separate package might have
been cleaner.
Are there easier ways of achieving essentially the same thing? Howabout
modules that would factor out the common bits even further, so that you
could state which hash member should have array-like access - in the
spirit of how Struct and MethodMaker automate matters. I'm still a bit
envious of Ruby's mixins and have been thinking of ways to implement
them in Perl, too.
And now the code, currently in a single file:
package TiedArray;
use strict; use warnings; use Tie::Array;
our @ISA = qw|Tie::Array|;
sub new
{ # Tie an array and bless the tied variable in $class.
my $class = shift;
my @array;
tie @array, $class, @_;
bless \@array, $class;
} # sub
sub TIEARRAY
{ # Implement as a hash with an array field.
my $self = { };
bless $self, shift;
$self->init(@_);
return $self;
} # sub
sub init
{ # Add fields and process arguments.
my $self = shift;
$self->{data} = shift;
} # sub
sub object
{ # Get the tied object given the tied variable $self.
my $self = shift;
tied @$self;
} # sub
# Pasted from Tie::StdArray with minor mods, as the array is in a hash.
sub FETCHSIZE { scalar @{$_[0]{array}} }
sub STORESIZE { $#{$_[0]{array}} = $_[1]-1 }
sub STORE { $_[0]->{array}[$_[1]] = $_[2] }
sub FETCH { $_[0]->{array}[$_[1]] }
# Methods for the underlying object:
sub data
{ # Accessor for the "data" hash member.
my $self = object(shift);
return $self->{data} unless @_;
$self->{data} = shift;
} # sub
sub size
{ # Getting the array size as a method.
my $self = shift;
return scalar @$self;
} # sub
1;
package main; # Some rather arbitrary tests.
my $array = TiedArray->new('stuff');
@$array[0 .. 1] = (qw|first second|);
print "Array: @$array\n";
print "Data: ", $array->data(), "\n";
$array->data('foo'),
push @$array, int(10 * rand) for 1 .. 4;
print "Array: @$array\n";
print "New data: ", $array->data(), "\n";
@$array = splice @$array, 1, 2;
print "Array: @$array\n";
print $array->size(), " elements.\n";
Thanks for any help in advance. I'm rather new to Perl OOP, especially
tied variables.
I'd like to treat an object like an array but also provide methods for
accessing bits of the object state that aren't array-like. An example in
which this would be useful comes from Ruby, its regexp Match objects are
indexable like arrays for accessing back references, yet they also have
a nice interface via methods. Can Perl also do objects having both
methods and hash or array-like indexing?
I think I've found one possible solution, for arrays at least, after
reading Object Oriented Perl Ch 9.7 on tied objects. You could have a
constructor that returns a blessed ref to an underlying tied array,
which the user accesses like any array ref. As it is tied, its easy to
make the array read-only, for instance, if more restricted access is
desired. As the object made by the constructor is a blessed scalar the
user can also call methods on it. One way to handle the methods, and
also be able to store object state beside the array itself, is to
implement the tied array as a blessed hash behind the scenes. When a
method in which the non-array bits of the object are needed gets
called, you can dig up the underlying object behind the tied array via
tied.
To my surprise, at least in this simple script, both the array and the
object usage seem to be working OK. On second thought, putting the
methods dealing with tied variables in a separate package might have
been cleaner.
Are there easier ways of achieving essentially the same thing? Howabout
modules that would factor out the common bits even further, so that you
could state which hash member should have array-like access - in the
spirit of how Struct and MethodMaker automate matters. I'm still a bit
envious of Ruby's mixins and have been thinking of ways to implement
them in Perl, too.
And now the code, currently in a single file:
package TiedArray;
use strict; use warnings; use Tie::Array;
our @ISA = qw|Tie::Array|;
sub new
{ # Tie an array and bless the tied variable in $class.
my $class = shift;
my @array;
tie @array, $class, @_;
bless \@array, $class;
} # sub
sub TIEARRAY
{ # Implement as a hash with an array field.
my $self = { };
bless $self, shift;
$self->init(@_);
return $self;
} # sub
sub init
{ # Add fields and process arguments.
my $self = shift;
$self->{data} = shift;
} # sub
sub object
{ # Get the tied object given the tied variable $self.
my $self = shift;
tied @$self;
} # sub
# Pasted from Tie::StdArray with minor mods, as the array is in a hash.
sub FETCHSIZE { scalar @{$_[0]{array}} }
sub STORESIZE { $#{$_[0]{array}} = $_[1]-1 }
sub STORE { $_[0]->{array}[$_[1]] = $_[2] }
sub FETCH { $_[0]->{array}[$_[1]] }
# Methods for the underlying object:
sub data
{ # Accessor for the "data" hash member.
my $self = object(shift);
return $self->{data} unless @_;
$self->{data} = shift;
} # sub
sub size
{ # Getting the array size as a method.
my $self = shift;
return scalar @$self;
} # sub
1;
package main; # Some rather arbitrary tests.
my $array = TiedArray->new('stuff');
@$array[0 .. 1] = (qw|first second|);
print "Array: @$array\n";
print "Data: ", $array->data(), "\n";
$array->data('foo'),
push @$array, int(10 * rand) for 1 .. 4;
print "Array: @$array\n";
print "New data: ", $array->data(), "\n";
@$array = splice @$array, 1, 2;
print "Array: @$array\n";
print $array->size(), " elements.\n";
Thanks for any help in advance. I'm rather new to Perl OOP, especially
tied variables.