Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: Class::InsideOut - yet another riff on inside out objects.

by John M. Dlugosz (Monsignor)
on Dec 18, 2002 at 22:14 UTC ( #220964=note: print w/ replies, xml ) Need Help??


in reply to Class::InsideOut - yet another riff on inside out objects.

Without regard to how it's actually implemented, I want to say that I like the idea of using the :Field attribute to declare instance data.

As for the source filter, I wonder if maybe you could use AUTOLOAD to generate the accessors when/if they are first used? The :Field attrib would store a reference to the hash and its name, and the generator would look it up to see if that name existed, without worrying about the scope of the underlying declared hash.

A benifit of the inside-out approach in general is that names of instance data can be reused in derived classes without conflict.

I don't understand your DESTROY. You define $class on one line to be my blessed class, then define it again on the next line to be every key in the Values hash. Isn't this going to destroy all classes? That is, don't you want a single $values=$Values{$class}, rather than iterating over all $Values?

—John


Comment on Re: Class::InsideOut - yet another riff on inside out objects.
Re^2: Class::InsideOut - yet another riff on inside out objects.
by adrianh (Chancellor) on Dec 18, 2002 at 23:52 UTC
    As for the source filter, I wonder if maybe you could use AUTOLOAD to generate the accessors when/if they are first used? The :Field attrib would store a reference to the hash and its name, and the generator would look it up to see if that name existed, without worrying about the scope of the underlying declared hash.

    The problem is in getting the name of the hash.

    If it's a global var then you can get at it via the GLOB Attribute::Handlers passes you. Which is easy.

    Unfortunately all you get for a lexical variable is the string 'LEXICAL'. Since at the point the handler is called the variable isn't in the pad yet, you cannot get at it with PadWalker either.

    As far as I can see the only way of getting the name for a lexically scoped hash is to use a source filter - but if anybody has a sneakier solution I'd love to hear it :-)

    I did briefly consider :

    my %foo : Field; # no accessor our %foo : Field; # accessor created automatically

    but I couldn't really find a justification for overloading the meaning of our/my in this way.

    I don't understand your DESTROY. You define $class on one line to be my blessed class, then define it again on the next line to be every key in the Values hash. Isn't this going to destroy all classes? That is, don't you want a single $values=$Values{$class}, rather than iterating over all $Values?

    The line is redundent - hangover from an earlier version. Well spotted. I've removed it.

    You don't want to just look at $Values{$class}, since you need to destroy inherited attributes too. So the DESTROY method deletes all instances of a particular object in all classes.

    Why the overkill? Why not just check the @ISA hierarchy for the object? Because it might have been blessed into another class (e.g. when implementing a series of state classes).

    Have a test script, just to reassure:

    use Test::More tests => 3; use strict; use warnings; { package Foo; use base qw(Class::InsideOut); sub new { bless {}, shift }; my %foo :Field; sub foo { my $self = shift->self; @_ ? $foo{$self} = shift : $foo{$self}; }; sub Num_objects { scalar(keys(%foo)) }; package Bar; use base qw(Class::InsideOut); }; { my $o1 = Foo->new; $o1->foo(1); { my $o2 = Foo->new; $o2->foo(2); bless $o2, 'Bar'; is( Foo->Num_objects, 2, '2 objects' ); }; is( Foo->Num_objects, 1, '1 object' ); }; is( Foo->Num_objects, 0, '0 objects' );

    Produces

    1..3 + ok 1 - 2 objects + ok 2 - 1 object + ok 3 - 0 objects +

    You could make it more efficient (e.g. overload bless and keep track of what classes an object has been and only examine those hierarchies) - but I thought I'd keep it simple for now!

      If you need to go hunting for lexicals then you start with PadWalker. If the lexical you are looking for isn't immediately visible then you might also use Devel::Caller to get the code references for other places in the calling stack. You'd then use PadWalker on those references to check for otherwise inaccessible lexicals. Eventually every lexical exists in some scope that itself is visible either from the symbol table or via the calling stack.


      Fun Fun Fun in the Fluffy Chair

        As I understand it the %hash isn't actually in scope when the ATTR handler is called, so there is no way to get at it with PadWalker...

        Demonstration.

        #! /usr/bin/perl use strict; use warnings; package Foo; use Attribute::Handlers; use PadWalker qw(peek_my); use Data::Dumper; sub Field : ATTR(HASH) { my ($class, $symbol, $hash) = @_; if ($symbol eq 'LEXICAL') { my $level=0; while (eval {peek_my($level+1)} && !$@) {$level++}; print Dumper(peek_my($level)); } else { print "got %", *{$symbol}{NAME}, " from symbol table\n"; }; }; package FooBar; use base qw(Foo); my %just_to_prove_we_are_in_the_right_scope; print "about to call ATTR with \\%foo\n"; my %foo : Field; print "about to call ATTR with \\%bar\n"; my %bar : Field;

        produces

        about to call ATTR with \%foo $VAR1 = { '%just_to_prove_we_are_in_the_right_scope' => {} }; about to call ATTR with \%bar $VAR1 = { '%just_to_prove_we_are_in_the_right_scope' => {}, '%foo' => {} };

        Unless I'm missing something (entirely possible) I don't see how Devel::Caller can help here? There isn't a way that (in the above example) you can get at the name 'foo' at the time the %foo hash reference is passed to the ATTR handler.

      The problem is in getting the name of the hash.

      I was thinking that a reference to the hash is passed to the attribute handler. I see now that the problem isn't finding the hash, but deciding on the name of the accessor to go with it! I suppose that's a flaw/oversight of the attribute stuff.

      Isn't there a way to get a symbol's name given a ref? Maybe it only works for functions or when debug mode is enabled...? But I thought I read about that somewhere.

      Update: I was thinking of the *whatever{NAME} syntax, which needs a glob not a ref.

        Outside of PadWalker and *whatever{NAME} there is nothing that I'm aware of.

      You don't want to just look at $Values{$class}, since you need to destroy inherited attributes too.

      Isn't that going to be done by that class's DESTROY method? That is, the "next" call will do it.

      So the DESTROY method deletes all instances of a particular object in all classes.

      I see, that won't wipe out everything, just all the attributes of that object since instance keys are unique.

        Isn't that going to be done by that class's DESTROY method? That is, the "next" call will do it.

        No. The NEXT is there in case we have inherited from another non-inside-out class that has its own DESTROY method.

        The idea is that all of the sub-classes of Class::InsideOut can now avoid writing custom DESTROY methods since the base-class handles it for them.

        Hopefully this makes some vague sort of sense :-)

      I think I have something for you.

      I've been raking my brain for ways of having the attribute routine somehow register something like a hook whose call is delayed so that by the time the hook is triggered, the hash is on the pad. I hadn't been able to come up with any approach so far.

      The idea I just came up with is simple: tie. :-)

      Tie the attribute hash temporarily. The first access to it will trigger a call whence PadWalker can hopefully locate it. The rest is details - create an accessor closure in the appropriate package and untie the hash.

      Unfortunately I'm not of much help since 5.6.1 manages the pads differently and I can't write test code to confirm this. But you should be able to make something of it.

      Makeshifts last the longest.

        The idea I eventually came up with is simple: tie.

        Sneaky. Like it. Can't see a reason why it wouldn't work off the top of my head. Might give it a whirl this weekend when I have some more time :-)

        Ah. Unfortunately, not that sneaky after all. Consider:

        { package Test; use base qw(Class::InsideOut); sub new { bless [], shift }; my %foo : Field; };

        %foo might now be tied but, since there is no way to access it, no magical attribute generation can happen :-(

        Only works if you need to access the hash elsewhere in the same class - which may not always happen.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://220964]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (15)
As of 2014-12-22 17:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (121 votes), past polls