http://qs321.pair.com?node_id=514605

xdg has asked for the wisdom of the Perl Monks concerning the following question:

I'm putting together an introductory talk for the NYC Perl Seminar group on inside-out objects. I'd like to present a mini-review of the strengths and weaknesses of various inside-out implementations on CPAN, so I'm trying to assemble a comprehensive list.

The list so far includes:

Have I missed any?

I'd also welcome any comments/experiences people have had with any of these. (For private comments, please email dagolden at cpan.org)

Finally, if anyone has done a private implementation of an inside-out class that they think is "best practice" and would be willing to share, please let me know. That could be inside a CPAN module (but not using an existing inside-out object framework) or could be something never released to CPAN.

Thanks,

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

Replies are listed 'Best First'.
Re: Seeking inside-out object implementations
by perrin (Chancellor) on Dec 06, 2005 at 20:19 UTC
    I haven't used any of these, for a couple of reasons. Well, three I guess, the first being that I haven't found any real problems with using simple hash-based classes for everything.

    The second is that Class::Std and some of the others do some things that I don't trust to work reliably and not interfere with some other tool. They use rely on DESTROY, have a super-complex constructor, use lots of subroutine attribute magic, etc. I suspect bugs and bad interactions with other modules will be found in these complex additions.

    And finally, I don't use them because, despite the name, they are the exact opposite of standard. All the Perl literature teaches programmers how simple hash-based objects work, and any OO Perl programmer can be expected to know it and read that code. Class::Std turns the world upside down (or inside out if you prefer) and provides a whole new mini-language to learn. It means another level of stuff that people will have to learn before they have any chance of understanding my code, and thus more training, more difficulty getting help in public forums like Perlmonks, fewer people able to contribute patches, etc. To me, that isn't worth the payoff.

    Encapsulation? I'm giving them the source! If they want to break it badly enough, they will be able to.

    UPDATE: I removed an earlier statement that Class::Std puts a DESTROY method in the UNIVERSAL:: package. Although the Perl Best Practices book does show that in the section on inside-out objects, the Class::Std module doesn't actually do it.

      Not having encapsulation means that it's really, really easy for someone to silently and mysteriously break your class by overwriting a hash key. I've had to deal with this and it's not fun. For smaller systems, it's less of a problem but bigger systems require more careful planning. Admittedly good test suites catch the errors, but they frequently don't tell you how to fix 'em.

      The main problem with inside-out objects is that they're currently even more of a hack than Perl's current OO system. With clean OO, we wouldn't even be worrying about this.

      I might add that Class::BuildMethods does not override UNIVERSAL::DESTROY and is very lightweight. It gives you encapsulation in a very easy to use manner and can be incorporated into existing classes. Further, the constructor is usually ridiculously easy to use. It's much easier than its counter-parts. The trade-off being that you only get one style of getter/setter and you can only store a single value (hashes must be passed as hashrefs, for example. By restricting the interface, the code is simpler and faster. You want three simple getter/setters?

      use Class::BuildMethods qw/name rank serial/;

      You can mix them with any others you want and add defaults or validation as needed.

      Cheers,
      Ovid

      New address of my CGI Course.

      You have just described my experience with Class::Std.

      It took me a while to grok it, longer to debug why my constructor wasn't working, then a bit more to peer into the guts and figure out why something I was used to doing (deriving auxilliary member variables from either a default value or its user-overridden replacement) wasn't going to work unless Damian changed how Class::Std works. I filed a feature request via RT, but the change might actually introduce subtle bugs into existing Class::Std derivatives, so I don't think it can be done.

      I've gone back to using hash-based objects by default, but I use the inside-out technique every once in a while. It came in handy when I wanted to associate some instance variables with a blessed filehandle. http://www.rectangular.com/svn/kinosearch/trunk/lib/KinoSearch/Store/InStream.pm

      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
      The second is that Class::Std and some of the others do some things that I don't trust to work reliably and not interfere with some other tool.
      Interesting. If Class::Std or "some of the others" interfere with some other tool, why do you blame Class::Std and some of the others, and not the other tool?
      And finally, I don't use them because, despite the name, they are the exact opposite of standard. All the Perl literature teaches programmers how simple hash-based objects work, and any OO Perl programmer can be expected to know it and read that code.
      First of all, "inside-out objects" as a name doesn't suggest anything about being standard or not. And if your argument makes sense, we'd still be using the indirect object notation, because that's how we used to teach programmers in the literature. Views change over time, and documentation isn't static.
      Encapsulation? I'm giving them the source!
      The point of encapsulation is that you don't need to read the source.

      I guess you don't bother to use strict, or lexical variables, either, do you? Why would you, you have the source!

      Perl --((8:>*
        First of all, "inside-out objects" as a name doesn't suggest anything about being standard or not.

        This is, unfortunately, a Damian-created issue, with a number of new modules released to CPAN with "::Std" as part of their names, coincident with the release of Perl Best Practices. "Class::Std" should not be read as "Class::Standard" as it's anything but. Pronouncing that suffix letter-by-letter gives a better sense of the caution and prophylaxis that should be considered before using it.

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

        If Class::Std or "some of the others" interfere with some other tool, why do you blame Class::Std and some of the others, and not the other tool?

        Have you looked at how Class::Std is implemented? It's pretty crazy. It includes some "worst practices" too, like use of the UNIVERSAL:: namespace.

        "inside-out objects" as a name doesn't suggest anything about being standard

        The name is Class::Std, i.e. "Class Standard."

        we'd still be using the indirect object notation, because that's how we used to teach programmers in the literature. Views change over time, and documentation isn't static.

        If Class::Std ever became a very common way to do things, to the point where the Perl man pages used it in their OO examples, I would drop this objection. I think the difference between Class::Std and normal OO perl is a lot bigger than these other examples you gave though.

        we'd still be using the indirect object notation, because that's how we used to teach programmers in the literature. Views change over time, and documentation isn't static.

        You're misunderstanding me. What I'm saying is that any attempt to enforce encapsulation is ultimately futile when the other person has the source and can simply change your code to expose things that they want. I will grant that it's much harder to do than it is with simple hash-based objects though.

        UPDATE: Class::Std does not actually put things in UNIVERSAL::. This technique is suggested in the inside-out objects section of the PBP book, but not used in Class::Std.

Re: Seeking inside-out object implementations
by Ovid (Cardinal) on Dec 06, 2005 at 20:09 UTC

    You can look at my Class::BuildMethods. It's extremely lightweight. The nice thing about it is you can slowly convert your objects to inside-out objects regardless of the implementation you currently use. This gives you a migration path.

    use Data::UUID; use Class::BuildMethods 'name', rank => { default => 'private' }, age => { validate => sub { my ($self, $age) = @_; die "Must be 18 or over" unless $age >= 18; }, }, id => { default => Data::UUID->new->get_str, validate => sub { die "id() is readonly" } }; sub new { bless [], shift } sub foo { my $self = shift; return $self->[0] unless @_; # lots of horribly complicated code $self->[0] = shift; return $self; }

    In the above example, you have a blessed array reference, but it can be a blessed typeglob, hashref, whatever. You can also add your own methods which are independent of the inside-out attributes that BuildMethods defines. This allows you to take an existing class and gradually encapsulate methods one at a time.

    Cheers,
    Ovid

    New address of my CGI Course.

      Wow -- I didn't realize you what you were doing under the hood before. I browsed the code. If I understand it correctly in my quick skim -- you're using the inside-out object technique, but storing all the accessor data in a lexical in the Class::BuildMethods file scope, keyed off the package that imported Class::BuildMethods and the instance refaddr. That's an extra hash lookup versus handwritten inside-out objects, but I guess lighter-weight overall than Class::Std without all the constructor crud.

      However, I think it will still be subject to the thread/winfork and serialization issues. That probably ought to noted in the docs and you might want to consider using CLONE as noted in Threads and fork and CLONE, oh my! and marking it as safe for 5.8 only.

      Still, that's a great example of what can be achieved with independence from the underlying implementation -- and why that's a useful feature. I'll probably use it as an example in the seminar.

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

        You're right that I should probably note issues with threading and forking. I've used fork in Perl only a few times and I've only used threads in Java as threaded Perl has never been terribly robust. As a result, since these aren't that common in Perl, I tend not to think about them.

        Cheers,
        Ovid

        New address of my CGI Course.

Re: Seeking inside-out object implementations
by diotalevi (Canon) on Dec 06, 2005 at 20:20 UTC
    Data::Postponed is the rare kind of module that is impossible to fully implement without inside-out objects. I hear it would be faster if I switched the object key fetching function from overload::StrVal to Scalar::Util::refaddr. That is, you cannot overload an object's native dereferencing and still access its guts.
Re: Seeking inside-out object implementations
by snowhare (Friar) on Dec 06, 2005 at 20:33 UTC

    Serialization via Data::Dumper or Storable seems to be the Achilles Heel of all of the inside-out implementations. It seems to me to be a Very Bad Thing(TM) that, by default, including a reference to almost any inside-out object in a larger data structure results in a data structure that cannot be serialized. And, of course, fixing that would require making it possible to break encapsulation via manipulation of the serialized instance - thus defeating the raison d'être of inside-out objects.

    I also severely dislike the use of 'lexical globals' inherent in them. It makes anything using them difficult to make completely thread-safe.

    In the quest for encapsulation enforcement, they may be throwing the baby out with the bathwater.

      In the quest for encapsulation enforcement, they may be throwing the baby out with the bathwater

      I guess the bathwater is inside-out objects, but what is the baby here? Do you mean an object that can be externally serialized and plays well with threads? Well, to be honest, these seem like pretty specific needs. Granted, the Hard Core Encapsulation you get from inside-out objects may be pretty specialized too, but why is your set of special needs more important than another? I'm not trying to convince you to use IOO, but your argument against it seems kind of hollow in my mind.

        Most people expect to be able to serialize things with Storable, and many modules like MLDBM or CGI::Session/Apache::Session depend on it. It's not an esoteric concern. Neither is easy debugging with Data::Dumper or the 'x' command in the debugger. Thread use may not be widespread, but serialization is.
      Serialization via Data::Dumper or Storable seems to be the Achilles Heel of all of the inside-out implementations. It seems to me to be a Very Bad Thing(TM) that, by default, including a reference to almost any inside-out object in a larger data structure results in a data structure that cannot be serialized.
      I find that assuming that if you "walk the reference recursively, remembering everything you find" you have reliably serialized your object a very shaky way of programming. I'd never, ever make such an assumption for code that was important.

      If I have the need to serialize objects, their classes shall have store/retrieve methods, whether they have been implemented with inside-out objects, hash-based objects, or something else. Anything else just stomps all over encapsulation, and results in code that doesn't deserve to be checked into source control.

      And, of course, fixing that would require making it possible to break encapsulation via manipulation of the serialized instance - thus defeating the raison d'être of inside-out objects.
      No. The point of inside-out objects isn't to prohibit messing with the internals. The point of inside-out objects is two-fold: preventing accidental messing with the internals, and giving you all the benefits of use strict.
      I also severely dislike the use of 'lexical globals' inherent in them. It makes anything using them difficult to make completely thread-safe.
      I do not know what 'lexical globals' are. And I don't think that inside-out objects are anyway less thread-safe than hash based objects are. (Now, certain modules claiming to do inside-out objects may not be thread safe).
      Perl --((8:>*
      Serialization via Data::Dumper or Storable seems to be the Achilles Heel of all of the inside-out implementations. ... And, of course, fixing that would require making it possible to break encapsulation via manipulation of the serialized instance - thus defeating the raison d'être of inside-out objects.
      Not any more. Object::InsideOut now supports serialization using Storable, and it does so without breaking encapsulation.

      Remember: There's always one more bug.
Re: Seeking inside-out object implementations
by petdance (Parson) on Dec 06, 2005 at 21:49 UTC
    List::Cycle. I specifically wrote it during the review phase of Perl Best Practices so that people could look at it as an example of how to do inside-out objects.

    xoxo,
    Andy

Re: Seeking inside-out object implementations
by Anonymous Monk on Dec 06, 2005 at 23:30 UTC
    Symbol::Glob. It's as close to a pure PBP inside-out object as possible. One interesting little hitch was that I needed to iterate over the slots in an object. Had to set up a mapper hash to map slot to storage.
Re: Seeking inside-out object implementations
by Perl Mouse (Chaplain) on Dec 06, 2005 at 23:48 UTC
    The list so far includes
    What I find amazing is that whatever style people suggest to code objects in, a gazillion modules appear, each attempting to implement that style in a different way. And each of them introduces another API to learn. What's worse, (almost) all of them come with enough drawbacks that one may wonder what the point of any of them are.

    Have I missed any?
    The obvious one: don't use a module.
    Perl --((8:>*
      The obvious one: don't use a module.

      My initial reaction to this snide remark was to dismiss it as yet another exercise in sarcasm. However, looking back at my post, I suppose I didn't make it clear that the purpose of the seminar is to teach the inside-out technique, and, particularly, its pros and cons. I also want to give an overview of the "gazillion" modules that are springing up, so people understand what each one brings or doesn't bring.

      As to the gazillion modules, what's the alternative? Every programmer going out and making their own gazillion mistakes writing everything by hand? At least CPAN modules, flawed as they might be, have the potential to benefit from some degree of peer review, experience and bug reports. (c.f. the growing Class-Std bug list)

      What I find slightly amazing is that none of the tech reviewers for PBP picked up on any of the potential design issues before Damian went out and declared inside-out objects as the a new best practice.

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

        As to the gazillion modules, what's the alternative? Every programmer going out and making their own gazillion mistakes writing everything by hand?
        That's like saying everyone should take a cab into work, instead of driving yourself and create car accidents.

        We've already established that all the modules have their flaws (otherwise, we'd quickly agree what module to use). And frankly, inside-out objects are simple. Hardly more complicated than hash based objects.

        Give programmers a bit of credit.

        Perl --((8:>*

      You may say "drawbacks", others might say "features". If a particular implementation does exactly what you need, why not use it?

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Seeking inside-out object implementations
by Codon (Friar) on Dec 09, 2005 at 00:37 UTC

    We are finishing up a re-architecture of one of our internal applications. Most of the design inherits from an Inside-out object that we developed (Common::Thing). It uses blessed scalar refs as the object. Every class that inherits from this class also inherits new(), copy(), seed(), dump(), as_hash(), and DESTROY() object methods and a make_attributes() class method. Common::Thing does some stash manipulations to make a getter/setter method for each attribute in the derived class's name space. A derived class would look something like:

    package My::Thing; use base qw(Common::Thing); __PACKAGE__->make_attributes( qw/ head should knees toes / ); # methods to do work go in here 1;

    It has worked well for our purposes, but certainly fails thread safety. We did quite a bit of work to stabalize our application around threads, but finally gave up when the fact that Perl is not thread-safe became overpoweringly obvious.

    Ivan Heffner
    Sr. Software Engineer, DAS Lead
    WhitePages.com, Inc.
      Making inside-out objects thread-safe is not a trivial process, but it can be done. Object::InsideOut is thread-safe, and even goes further by providing support for threads::shared.

      You might consider converting your Common::Thing module into a wrapper around Object::InsideOut to give you the functionality you need.


      Remember: There's always one more bug.