http://www.perlmonks.org?node_id=617975


in reply to Re^2: Beyond Inside-Out (fluff)
in thread Beyond Inside-Out

Something like:

sub ego { $_[0] ||= {}; my $corona= shift @_; my $pkg= caller(); for my $ego ( $corona->{$pkg} ) { $_= shift @_ if @_; $_ ||= {}; return $_; } }

Yes, a much simpler and more direct solution to some of the problems that "inside-out" objects are being promoted to address1.

Note that this suffers from the usual short-comings of caller. The calling "package" is quite an ambiguous thing and calller returns only one meaning of that. I have personally written methods that have many different values for the different interpretations of "calling package" and I bet your module would fail in such situations (just like SUPER:: and its replacements fail).

Consider:

package Devel::HandyMethods; sub Handy::GetOrSet { my $self= ego( shift @_ ); # ... } package My::Module::_private; *My::Module::foo= \&Handy::GetOrSet; sub My::Module::bar { my $self= ego( shift @_ ); # ... }

It is unfortunate that caller provides no way to get "My::Module" when Handy::GetOrSet() gets called via "SubclassOf::My::Module"->foo() (assuming @SubclassOf::My::Module::ISA= 'My::Module'). So you should provide ways for module authors to override how you determine which package name to use.

And thanks for pointing out some of the stupidity of "inside-out" objects. I still boggle a bit that such a convoluted technique for inheriting from objects that were not designed to be inherited from has become the most touted method for doing all Perl OO. Saner alternatives need more touting. (:

- tye        

1 Note that "inside-out" objects were first created to address being able to subclass arbitrary objects but "inside-out" objects are now being heavily touted as the way to implement all objects and your idea certainly has a lot of advantages as far as a "best practice" way of implementing objects. But your method doesn't deal with the original "subclass arbitrary object" problem.

Also, you don't address the "compile-time checking of member variable names" issue that "inside-out" objects address. Some would "use fields" as another solution. I prefer to have an array for an object and use constant subroutines for member variable names.

Replies are listed 'Best First'.
Re^4: Beyond Inside-Out (details)
by Anno (Deacon) on May 29, 2007 at 15:57 UTC
    Something like:
    sub ego { $_[0] ||= {}; my $corona= shift @_; my $pkg= caller(); for my $ego ( $corona->{$pkg} ) { $_= shift @_ if @_; $_ ||= {}; return $_; } }
    Yes, a much simpler and more direct solution to some of the problems that "inside-out" objects are being promoted to address1.

    Yes, that's similar with one important difference: In your implementation, the object proper is its corona (a hash). The way I have it in Alter, the object itself can be whatever it is, the corona is attached via magic, and accessed through the magical ego() function.

    That leaves the possiblity for the object proper (the "carrier") to be whatever it needs to be to support a conventional class. That answers the question of mixed inheritance. Like with inside-out, any class can inherit from an Alter-based class, and in reverse, an Alter-based class can inherit (without further ado) from one conventional class. Both work by making the conventioonal object the carrier.

    As for method-borrowing, a la

    package Foo; *brrowed_meth = \ &Bar::meth;
    why, yes, the borrowed method would continue to access the alter ego that was established in Bar. That's what I would expect in any case.

    As to the more general problem of the various meanings of caller, if I'm not mistaken what I'm using is the package that was in force when the call to ego() was compiled. That is excatly what I want: Mehods compiled in a class access the alter ego specific to that class, and that's the only way.

    Anno

      what I'm using is the package that was in force when the call to ego() was compiled. That is excatly what I want: Mehods compiled in a class access the alter ego specific to that class, and that's the only way.

      Yes, you are using the package (not "class") that the method was compiled in. But, no, you appear to have nearly completely missed my point. The package that a subroutine was compiled in can have very little to do with the class that the method ends up being a part of. In my example, the "class" you'd end up using isn't even a class, just the name of a package where code for some subroutines happened to be compiled.

      The correct thing to look up is not what package the code was compiled into, nor the package part of the orignal name given to the subroutine (the two things that caller can give you). The correct "class" is the package part of the subroutine name that was used to find the subroutine when the method was looked up (looking in symbol tables and following @ISA). Unfortunately, I have yet to see a way to get this information. That is why you'd need to provide a way to override how the class name is determined.

      Ah, so you are using Perl "magic" and you are allowing for "uncooperative" subclassing. If you'd include the XS code then this would have been clearer. I can't comment much on this because I'd still be guessing at what you are really doing (no, I'm not going to download the package and extract the source this morning), though I consider using "magic" to take much of the "simpler" benefit away from your idea.

      - tye        

        Yes, you are using the package (not "class") that the method was compiled in. But, no, you appear to have nearly completely missed my point.

        Entirely possible.

        The package that a subroutine was compiled in can have very little to do with the class that the method ends up being a part of.

        True, but I'm not too worried about that. It doesn't happen often in practice and is usually just done for convenience and can be avoided. But yes, it's something a user would have to be warned about. I am indeed assuming that the code for method Foo::meth was compiled with package Foo in force.

        The correct "class" is the package part of the subroutine name that was used to find the subroutine when the method was looked up (looking in symbol tables and following @ISA). Unfortunately, I have yet to see a way to get this information.

        You mean, when a method is called qualified, as in $obj->Foo::meth? Two things can happen: The method is actually found in class Foo, in which case I assume its code was also compiled there, so that's okay. Or Foo itself inherits the method from Bar, in which case the Bar's incarnation of the object is used. That is also how it's supposed to work.

        That is why you'd need to provide a way to override how the class name is determined.

        You haven't quite convinced me with the specific argument, but general experience shows that for all caller-sensitive functions there comes a time when you want to override the caller. Run-time compiling code in the right package (sometimes the only way out) is too ugly. I guess I'll provide a way to specify a class different from your own just in case.

        I have appended the XS code for Alter::ego, it isn't that much.

        Anno

        /* id-key for ext magic */ #define ALT_EXT_ALTER 6693 MODULE = Alter PACKAGE = Alter SV* ego(...) PROTOTYPE: $@ CODE: SV* obj = ST(0); SV* given = items > 1 ? ST(1) : NULL; HV* alt_tab; SV** alt_ptr; if (SvROK(obj)) { char* class = CopSTASHPV(PL_curcop); SV* self = SvRV(obj); MAGIC* mg; if (SvTYPE(self) < SVt_PVMG) SvUPGRADE(self, SVt_PVMG); for (mg = SvMAGIC(self); mg; mg = mg->mg_moremagic) { if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == ALT_EXT_ALTER) ) break; } if (!mg) { alt_tab = newHV(); mg = sv_magicext(self, (SV*)alt_tab, PERL_MAGIC_ext, NULL, + NULL, 0); mg->mg_private = ALT_EXT_ALTER; } else { alt_tab = (HV*)mg->mg_obj; } if (alt_ptr = hv_fetch(alt_tab, class, strlen(class), 0)) { RETVAL = newRV_inc(SvRV( *alt_ptr)); } else { if (!given) { /* should probably croak, but we decree a ha +sh */ given = newRV_inc((SV*)newHV()); } else { /* need a non-mortal ref */ given = newRV_inc(SvRV(given)); } hv_store(alt_tab, class, strlen(class), SvREFCNT_inc(given +), 0); RETVAL = given; } } else { RETVAL = NULL; } OUTPUT: RETVAL
        The correct thing to look up is not what package the code was compiled into, nor the package part of the orignal name given to the subroutine (the two things that caller can give you). The correct "class" is the package part of the subroutine name that was used to find the subroutine when the method was looked up (looking in symbol tables and following @ISA). Unfortunately, I have yet to see a way to get this information. That is why you'd need to provide a way to override how the class name is determined.

        You mean, runtime overriding methods for an object after the fact of its construction by manipulating the @ISA chain, while still expecting access to the original object data? Could you name me some Modules which do that devilry so I can immediately scratch them from my list of useful modules, if only for lack of understanding and never wishing to have to debug them? Or is it just that I can't follow?

        If you mean altering the inheritance chain to make the object mutate and show a distinct behaviour depending on the current context, that works fine with Anno's approach.

        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}