Both Methods and Indexing for Objects?

  • Thread starter Veli-Pekka Tätilä
  • Start date

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.
 
Ad

Advertisements

A

anno4000

Veli-Pekka Tätilä said:
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?

You can overload de-referencing in your class. That won't make objects
look like arrays, but like array references, which may be good enough.

I've made up a Regex class that behaves a bit like what you describe.
After a match, an object can be used like an array ref that holds
the captured strings (with the entire match in array position 0).

Anno

-----------------------------------------------------------------------

my $r = Regex->new( '(.)(.)(.)');
$r->match( 'abc');

print "$r->[ $_]\n" for 1 .. 3;
print "@$r\n";


package Regex;

sub new {
my ( $class, $re) = @_;
bless { re => qr/$re/, capt => [] }, $class;
}

sub match {
my ( $r, $str) = @_;
@{ $r->{ capt} } = $str =~ /($r->{ re})/;
return scalar @{ $r->{ capt} };
}

use overload '@{}' => sub { shift()->{ capt} };
__END__
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top