Yenya's World

Thu, 22 Sep 2005

Inside-out objects in Perl

In Perl, objects are represented as blessed references. The traditional approach is to use the hash reference, with object attributes represented as members of this hash:

package Beverage::Whisky;

sub new {
	my $class = shift;
	my %args = @_;
	my $self = bless { }, $class;

	$self->{name} = $args{name};
	$self->{age} = $args{age};

	return $self;
}

sub get_age {
	my $self = shift;
	return $self->{aqe};
}

# ... etc ..., and then somewhere else

my $bottle = Beverage::Whisky->new(name => 'Laphroaig', age => 10);
print 'This bottle is aged ', $bottle->get_age(), " years\n";

The main problem with this approach is that when you make a typo (did you notice that I accidentally wrote aqe instead of age in the get_age() method?), new hash member is silently added and no error is reported. Another problem is that nobody can stop the module/class user to access the attributes directly and possibly mess up the internal state of the object.

In Perl Best Pracices Damian Conway suggests that "inside-out" objects should be used instead. I.e. the objects where the attributes are not stored in the object itself, but referenced from the global hash defined in the class itself:

package Beverage::Whisky:

use Class:Std::Utils;

{
   my (%name_of, %age_of);

   sub new {
	my $class = shift;
	my %args = @_;
	my $self = bless \do { my $anon_scalar; }, $class;

	$name_of{ident $self} = $args{name};
	$age_of{ident $self} = $args{age};

	return $self;
   }

   sub get_age {
	my $self = shift;
	return $aqe_of{ident $self};
   }
}

Note that this approach is not any longer than the previous version, and a similar typo in get_age() method above leads to an immediate compile-time failure. The object itself is a blessed reference to an empty scalar, which is hard to make a mess of. The per-class attribute hashes are in a local block, so they cannot be accessed directly from the outside of this block.

However, there is something missing from the "inside-out" code, and adding this voids Damian Conway's claim about the same code size:

    sub DESTROY {
	my $self = shift;
	delete $name_of{ident $self};
	delete $age_of{ident $self};
    }

Without the destructor each object would leak memory used for storing its attributes. But with the destructor the code is longer and thus more error-prone. The "inside-out" approach has also serious drawback in threaded code - while the traditional approach requires no locking in the methods itself, provided that one object is accessed by one thread only, the "inside-out" approach requires access to the per-class global hashes, which implies locking and a potential performance penalty.

In my opinion the inside-out approach is interesting, but it has serious drawbacks which prevent me from using it instead of the traditional "blessed hash reference" approach. I don't need to enforce the encapsulation of objects anywhere else than in the documentation. I just want the typos in the attribute name show up either immediately (preferred) or in the run-time. Is there a better approach to objects in Perl, which would solve this problem? Maybe locked hash keys (as provided by the Hash::Util module) is what I need. Any other recommendations?

Section: /computers (RSS feed) | Permanent link | 4 writebacks

About:

Yenya's World: Linux and beyond - Yenya's blog.

Links:

RSS feed

Jan "Yenya" Kasprzak

The main page of this blog

Categories:

Archive:

Blog roll:

alphabetically :-)