Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

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

by adrianh (Chancellor)
on Dec 18, 2002 at 23:52 UTC ( [id://220999]=note: print w/replies, xml ) Need Help??


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

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!

Replies are listed 'Best First'.
Re^3: Class::InsideOut - yet another riff on inside out objects.
by diotalevi (Canon) on Dec 19, 2002 at 06:03 UTC

    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.

        Hmm...
        $VAR1 = { '%bar' => {}, '%just_to_prove_we_are_in_the_right_scope' => {}, '%foo' => {} }; $VAR1 = { '%bar' => {}, '%just_to_prove_we_are_in_the_right_scope' => {}, '%foo' => {} }; about to call ATTR with \%foo about to call ATTR with \%bar
        Perl 5.6.1, PadWalker 0.08, Attribute::Handlers 0.78

        Makeshifts last the longest.

        Oh ok. I was expressing more of a generalized ability so I might combine that with a search of all the contexts I could find on the symbol table as well. Very slow, not the right approach but still doable. So that'd just have to go all around and collect the pads from every visible code reference and I'm thinking that eventually you hit the right one...


        Fun Fun Fun in the Fluffy Chair

Re^3: Class::InsideOut - yet another riff on inside out objects.
by Aristotle (Chancellor) on Dec 19, 2002 at 22:07 UTC

    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.

      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.

        Argh. Of course. I'm starting to feel a bit stupid. I should get 5.8 myself and start playing around so I don't miss the obvious loopholes in my ideas.

        At any rate it looks as though we'll have to get out the heavy hitter to crush this one..

        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 :-)

Re: Re: Re: Class::InsideOut - yet another riff on inside out objects.
by John M. Dlugosz (Monsignor) on Dec 19, 2002 at 17:12 UTC
    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.

Re: Re: Re: Class::InsideOut - yet another riff on inside out objects.
by John M. Dlugosz (Monsignor) on Dec 19, 2002 at 19:39 UTC
    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 :-)

        Hmm, if the instance held a list of all valid attributes, then you would not need to search all attributes for all classes.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (6)
As of 2024-03-28 08:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found