http://qs1969.pair.com?node_id=576707

When I was at YAPC::2006, a talk of chromatic's got me to thinking about a better way of producing inside-out objects. My idea proved troublesome, but here's the interface I wanted:

use Encapsulation; sub new { bless {}, shift; } sub foo { my $self = shift; return $self->{foo} unless @_; $self->{foo} = shift; } 1;

In other words, I wanted inside-out objects to act like a normal blessed hash. You know, if inside-out objects were that simple, more people would use them. I've stopped using them because they're so painful to me. I was talking about this idea to adrianh and he suggested a different approach than my foolish attempt to use a tied and blessed hash. Then he sent me the code.

#!/usr/bin/perl use strict; use warnings; { package Encapsulate; use Scalar::Util; BEGIN { my $Secrets = {}; use overload '%{}' => sub { my $id = Scalar::Util::refaddr( shift ); my $package = caller; return $Secrets->{ $id }->{ $package } ||= {}; }, fallback => 1; sub DESTROY { my $id = Scalar::Util::refaddr( shift ); delete $Secrets->{ $id }; }; } } { package Foo; use base qw( Encapsulate ); sub new { my $class = shift; return bless {}, $class; } sub foo { my $self = shift; $self->{ secret } = shift if @_; return $self->{ secret }; } } { package Bar; use base qw( Foo ); sub bar { my $self = shift; $self->{ secret } = shift if @_; return $self->{ secret }; } } use Test::More tests => 6; isa_ok my $o = Bar->new, 'Bar'; $o->foo( 42 ); is $o->foo, 42, "can set and get value of Foo's secret"; Bar->new->foo( 24 ); is $o->foo, 42, "different objects get different secrets"; $o->bar( 99 ); is $o->bar, 99, "can set and get value of Bar's secret"; is $o->foo, 42, "secrets of different classes do not interfere"; ok !defined $o->{secret}, 'cannot reach into objects';

It's not complete (no serialization support, for one thing), but wow, that's pretty easy! So go ahead, shoot holes in this approach.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Better Inside-Out Objects :)
by xdg (Monsignor) on Oct 06, 2006 at 17:18 UTC

    You don't seem to really want "inside-out" objects -- you seem to want encapsulated objects. Inside-out is just one way of doing that and it seems like you're going out of your way to try to make it act like something else.

    A couple quick observations:

    • Is there exta overhead for overloading?

    • What if Foo or Bar want to overload "%{}" themselves?

    • You give up compile time typo protection. e.g. $self->{ sercet }

    I'll let you know if more things occur to me later.

    Update

    The more I think about it, the more this just seems like a way of disguising an accessor call by way of overloading.

    And there's no reason the trick requires inside-out objects. Here's a plain-old blessed hash version -- and it still gives you package-specific hash values that don't collide:

    I guess I'm not clear on what you find so difficult about inside-out objects (assuming a sane inside-out class generator to handle destruction, serialization, threads, etc.). Is "$foo{ id $self }" so much more difficult than "$self->{foo}"? Or even "$self->foo()"?

    What are you trying to optimize for? The hash-based interface does avoid having to pre-declare any properties -- you can just say "$self->{baz}" and the "baz" property springs into existence. That comes at the cost of the typo checking.

    I can also see a use for it if you're trying to upgrade legacy code. You can just drop "use base 'Encapsulate'" into your object modules and they should continue working like normal without having to edit anything else there, but everything else external that accesses objects will break.

    But just as is, this feels close to an XY Problem to me.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      OK, I'll grant that I don't get the compile-time attribute safety and thus this isn't really "inside-out" in the full sense of the word. However, it's not an XY problem because the specific problems that I deal with over and over again are twofold: one, people constantly violate encapsulation. Two, the compile-time safety isn't much of an issue for me because I write enough tests for my code (caveat: when working in a sane environment), that I catch the bugs up front.

      Further, the syntax of various inside-out object modules is just frickin' painful to me and I've used them and tried to evangelize them! When I try to get others to used them, I invariably get a huge amount of resistance along the lines of "blessed hashes are good enough for me". Regardless of whether or not it's rational, when you're on a team of five programmers and the other four agree with the concept but flat-out reject the implementations, you have to know when to cut your losses.

      For me, it's the encapsulation which is my biggest concern. If I can provide a way that programmers won't violate encapsulation but it's very easy for to migrate to (with sane code, nothing breaks!), then I've solved the exact problem I'm faced with. If you're trying to solve a different problem, then the XY problem applies.

      Cheers,
      Ovid

      New address of my CGI Course.

        the syntax of various inside-out object modules is just frickin' painful to me

        So, specifically, the whole "id $self" stuff? I can see that, though I suspect it's all about what people are used to seeing.

        I think the overload-like-a-hash approach will be problematic in the long run because the apparent simplicity masks significant complexity.

        Serialization is probably the first place this will really pop up and fail to behave like people expect. What happens when someone tries to throw one of these objects at Data::Dumper et al? What happens when someone tries to create an object on the fly by loading data into a hash reference manually and then blessing it into an object? (E.g. loading YAML or other external config data?)

        my $ref = { foo => 23, bar => 42, }; bless $ref, "Bar";

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Encapsulation as aid to debugging
by chrisdolan (Beadle) on Oct 06, 2006 at 20:44 UTC
    Rather than focus on the inside-outness (which, as others have commented, is questionable) how about thinking about this as a "strict" mode for object accesses. Below is a modified version of the above code, optimized for catching attempts to break encapsulation. It croaks if an external caller tries to access hash values directly.
    { package Encapsulate; use Scalar::Util; use Carp; my $Secrets = {}; use overload '%{}' => sub { my $obj = shift; my $caller_pkg = caller; my $pkg = ref $obj; if ( !$pkg->isa( $caller_pkg ) && !$caller_pkg->isa( $pkg )) { croak "Illegal attemp to access $pkg " . "internals from $caller_pkg"; } my $id = Scalar::Util::refaddr( $obj ); return $Secrets->{ $id } ||= {}; }, fallback => 1; sub DESTROY { my $id = Scalar::Util::refaddr( shift ); delete $Secrets->{ $id }; } } { package Foo; use base qw( Encapsulate ); sub new { my $class = shift; return bless {}, $class; } sub foo { my $self = shift; $self->{ secret } = shift if @_; return $self->{ secret }; } } { package Bar; use base qw( Foo ); sub bar { my $self = shift; $self->{ secret } = shift if @_; return $self->{ secret }; } } { package Quux; use base qw( Encapsulate ); sub new { my $class = shift; return bless {}, $class; } sub foo { my $self = shift; $self->{ secret } = shift if @_; return $self->{ secret }; } } use Test::More tests => 7; my $o = Bar->new; isa_ok $o, 'Bar'; $o->foo( 42 ); is $o->foo, 42, "can set and get value of Foo's secret"; Bar->new->foo( 24 ); is $o->foo, 42, "different objects get different secrets"; Quux->new->foo( 24 ); is $o->foo, 42, "different classes get different secrets"; $o->bar( 99 ); is $o->bar, 99, "can set and get value of Bar's secret"; is $o->foo, 99, "secrets of subclasses are shared"; eval { $o->{secret}; }; ok $@, 'cannot reach into objects';
      $o->bar( 99 ); is $o->bar, 99, "can set and get value of Bar's secret"; is $o->foo, 99, "secrets of subclasses are shared";
      can you explain the intent there? why should setting bar also change foo?

        foo and bar aren't properties. They're both alias accessors to a single, private property called secret.

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Better Inside-Out Objects :)
by jdporter (Paladin) on Oct 06, 2006 at 17:46 UTC

    That's slick, but it has nothing to do with inside-out objects. With inside-out objects, the point is to have one variable per column rather than one per row, to put it in table terms. It lets you take advantage of the fact that the set of columns is static (per class), so you can use my variables to hold them. The advantage, specifically, is that you get name checking at compile time, which you don't (normally) get for hash elements.

    I think (though I'm not sure) that the approach you've shown doesn't invert the row/column order (it's this inversion that gives "inside-out" its name)... but it certainly doesn't allocate one compiletime-checkable variable per column (i.e. field).

    We're building the house of the future together.
Re: Better Inside-Out Objects :)
by dws (Chancellor) on Oct 07, 2006 at 04:02 UTC

    I've been puzzled by Inside-Out Objects. They've attracted a following, but they seem like a complicated, long-winded way of solving a problem that I don't have. You lose serialization. You lose some ease in debugging. And you gain what? Better support for data encapsulation? I can achieve the same--and I've seen a team achieve the same--with a coding guideline that says "don't peek or poke at someone else's internals unless it's really O.K." and the discipline to follow the guideline.

      One of the big things you gain is orthogonal property storage and "black box" inheritance. With inside-out objects, you can subclass any other class regardless of the type of blessed reference (even if it changes in the future). In addition, you can freely add a private "_foo" property without regard for whether any super or subclass might now or in the future have a "_foo" property. This is a benefit if you (or your team) doesn't necessarily control every class in the class hierarchy. E.g. CPAN. This flexibility has substantial value for some people.

      (Of course, some implementations of inside-out objects choose to make this difficult in order to provide other features.)

      For more on the pros and cons of inside-out objects, see my YAPC::NA presentation: Eversion 101. I think it's a fairly balanced perspective on what people get and what the complexity is.

      I'm not an advocate, but I do think there's a lot of misinformation out there.

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      That coding guideline is the problem I have faced quite a bit. Working with teams of developers who don't appreciate the merits of encapsulation sometimes means adopting a different strategy. As a result, I've reluctantly concluded the sometimes one has no choice but to try a different way. Additionally, I've found myself violating encapsulation at times when it's innapropriate (I know some will disagree, but derived classes shouldn't be looking at their parent's privates).

      It's been pointed out that this strategy is essentially "use strict 'encapsulation'" and frankly, I'm happy with that as it solves the problem I wanted solved. Plus, when you're ready to move into production, if it's done properly, you can remove the encapsulation and everything still works (or you can leave it in if performance is not a problem).

      Cheers,
      Ovid

      New address of my CGI Course.

        Working with teams of developers who don't appreciate ...

        That gets to the core of one of the problems I'm having with Inside-Out Objects. At one level, they're an attempt to apply a technical solution to a people problem. Sometimes that works, but other times, it just adds weight to the burden the project has to bear.