[Phoenix-pm] inside out objects

Scott Walters scott at illogics.org
Wed Nov 23 09:06:35 PST 2005


Hi Michael,

I thought it was a cute trick, but I'm surprised it's recommended.
What I do depends on the situation -- the client, the style of the code 
I'm adding to, etc. 

When I don't necessarily care about encapsulation, I use an excellent
package by Juerd called Attribute::Property:

       package Person;
       use Attribute::Property;

       sub new  : New;
       sub name : Property;
       sub age  : Property { /^\d+\z/ and $_ > 0 }

       sub print_stats {
           my $self = shift;
           print "name: ", $self->name, "\n";
           print "age: ", $self->age, "\n";
       }

       sub get_older {
           my $self = shift;
           $self->age++;
       }

       package main;

       my $person = Person->new(name => 'Fred', age => 23);
       $person->get_older;
       $person->name = "Fred Worth";
       $person->print_stats;

A::P creates new methods for you that initialize instance variables from
arguments (just like in Perl 6 -- hence _Perl 6 Now_ including discussion
of it), and it also creates lvalue accessors for the instance data 
when you use the :Property attribute of subroutines. It's a lot less
code to write and the code looks a lot better.

For stuff I use internally, when I want some ecapsulation and not to have to
shift $this, I often use a little package called hashclosure.pm. hashclosure
basically tells Perl that the object's methods are code references inside 
of the hash. The hash contains methods and code references rather than
instance data. Instance data is "my" variables lexically closed over by the 
methods.

  package hashclosure;
  
  sub import {
      my $caller = caller;
      *{$caller.'::AUTOLOAD'} = sub {
          my $method = $AUTOLOAD; $method =~ s/.*:://;
          return if $method eq 'DESTROY';
          my $this = shift;
          local *{$caller.'::this'} = $this;
          if(! exists $this->{$method}) {
              my $super = "SUPER::$method";
              return $this->$super(@_);
          }
          $this->{$method}->(@_);
      }; 
  }
        
  1;

This is the AUTOLOAD glue needed to so that objects created as 
follows work:

  package Person;
  use hashclosure;

  our $this;

  sub new {
      my $class = shift;
      my $name;
      my $age;
      bless { 
          name => sub { $name },
          set_name => sub { $name = shift },
          age  => sub { $age },
          set_age => sub { $age = shift },
          print_stats => sub {
              my $self = shift;
              print "name: ", $self->name, "\n";
              print "age: ", $self->age, "\n"; 
          },  
          get_older => sub {
              my $self = shift;
              $self->age++;
          },
      }, $class;
  } 

Since the "instance data" is lexically closed over by the methods and
scoped to the new { } block, encapsulation is pretty good (PadWalker
and such can still get to it, but XS can do anything). 

Best of all, you don't have to write that annoying $self->{foo} crap --
just $foo will do. That's an improvement even over $self->foo as in A::P.

The AUTOLOAD logic shifts $this for us and sticks it and puts it into
the $this defined by 'out $this'.

Downsides are lack of lvalue accessors (so you can't do $person->name = "Fred")
and the ugly initialization syntax. 

This might be workable with lvalue, but I'd have to do a much larger 
and messier AUTOLOAD to make it happen and my first attempt didn't
pan out.

At the last meeting, I got out my Object::Lexical on the projector and
shoved that in people's faces. It also makes instance data into 
lexicals, but the implementation is completely different (it creates
a new stash for each object created, stuffs closures into it, and
blesses it -- it's probably the strangest thing I've ever done in Perl).
Here's what code using it looks like:

  use Object::Lexical;
  use Sub::Lexical;

  sub new {

    our $this;
    my $name;
    my $age;

    my sub age { $age };
    my sub name { $name };

    sub print_stats {
        print "name: ", $name, "\n";
        print "age: ", $age, "\n";
    }
     
    sub get_older {
        $age++;
    }

    instance();

  } 

It just doesn't get any more clear or concise than that for creating
objects in Perl. instance() serves the same purpose as bless(),
but it does the actual stash-blessing and closure-generating. 
To get this pretty syntax, you need a source filter -- that's what
Sub::Lexical does. For some other idioms that don't use a source
filter, see http://search.cpan.org/~swalters/Object-Lexical-0.02/Lexical.pm.
This module is a bit buggy, by the way, but if anyone actually
has any interest in it, I'll fix the thing.

Regards,
-scott

On  0, Michael Friedman <friedman at highwire.stanford.edu> wrote:
> So, I just read through _Perl Best Practices_ and it's fantastic.  
> It's even well written. And I agree with almost all of Damian's  
> recommendations for making more maintainable Perl code... all except  
> inside out objects.
> 
> However, as I've already had one tranformative religious experience  
> from this book (I've changed my braces style), I'm willing to give  
> the guy a chance on this one. But I need some more evidence.
> 
> Has anyone used inside out objects before? I completely believe that  
> they handle encapsulation much better, but what I don't buy is that  
> they're actually easier to maintain and equivalently easy to  
> understand as the "regular" hash-based objects are.
> 
> So, anyone have real world advice on using them?
> 
> -- Mike
> 
> PS - For those not in the know, inside out objects are identified by  
> a unique scalar that acts as an index into a list of hashes, one hash  
> for each attribute/field of the object. The values are held in the  
> set of hashes in a closure, so absolutely no one but the class itself  
> can access them. There is an example in the code block on http:// 
> www.windley.com/archives/2005/08/best_practices.shtml, and other  
> examples elsewhere that I can't seem to find at the moment. :-(
> 
> ---------------------------------------------------------------------
> Michael Friedman                     HighWire Press
> Phone: 650-725-1974                  Stanford University
> FAX:   270-721-8034                  <friedman at highwire.stanford.edu>
> ---------------------------------------------------------------------
> 
> 
> _______________________________________________
> Phoenix-pm mailing list
> Phoenix-pm at pm.org
> http://mail.pm.org/mailman/listinfo/phoenix-pm


More information about the Phoenix-pm mailing list