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?