Re: Moose Question

[email protected] (Stevan Little)
Newsgroups perl.moose
Message-ID <[email protected]>
Christopher,

Okay, this is a weird one actually.

Basically (as best I can tell) what is happening here is that the  
anon-class being created in BUILD is going out of scope and so being  
garbage collected (we actually remove the entire package in the  
DESTROY call). Then when you call ->meta later on and add a methods  
to it, it is actually creating a new metaclass for what is now an  
empty package ("Class::MOP::Class::__ANON__::SERIAL::2") and adding  
the methods to that. The reason the $chris instance is not able to  
call the is_fun method is because Perl's built in method caching is  
still looking at the original Class::MOP::Class::__ANON__::SERIAL::2  
package (which was cleared in the DESTROY) and not at the newly  
created version of Class::MOP::Class::__ANON__::SERIAL::2.

If you replace the BUILD code with this:

     my $meta = $self->meta->create_anon_class(
	superclasses => [__PACKAGE__],
	methods      => $methods,
     );
     print $meta;
     $meta->rebless_instance($self);

It will print something like:

     Moose::Meta::Class=HASH(0x18ff694)

And then replace the test code  with this:

     my $meta = $chris->meta;
     $meta->add_method( "is_fun", sub { print $_[0]->name . " is  
fun"; } );

     print $meta->get_method_list;
     print $meta->superclasses;
     print Class::MOP::Class::__ANON__::SERIAL::2->can('is_fun') ?  
'true' : 'false';
     print $chris->can('is_fun') ? 'true' : 'false';
     print $meta;
     print $meta->name;
     print $chris->dump;

     $chris->is_fun; # FAIL!

You get the following:

     is_fun

     true
     false
     Moose::Meta::Class=HASH(0x18ff778)
     Class::MOP::Class::__ANON__::SERIAL::2
     $VAR1 = bless( {
                  'name' => 'Chris',
                  'occupation' => 'Regular Programmer'
                }, 'Class::MOP::Class::__ANON__::SERIAL::2' );
     Can't locate object method "is_fun" via package  
"Class::MOP::Class::__ANON__::SERIAL::2" at foo.pl line 107.

The first line is the method_list, so we know that add_method  
succeeded. The second line, which is empty, is the superclasses. It  
should be 'person_2', but it is nothing, which indicates that this is  
a new version of the Class::MOP::Class::__ANON__::SERIAL::2 package.  
The third line shows that Class::MOP::Class::__ANON__::SERIAL::2 does  
indeed have the methods, but as the forth line indicates, the version  
of the package that $chris was blessed into disagrees. The 5th line  
shows that it is indeed a different metaclass (see the output of  
"print $meta" from the BUILD). The 6th line shows that the $meta is  
indeed connected to the Class::MOP::Class::__ANON__::SERIAL::2  
package though, and the 7th line confirms that $chris is also blessed  
into it too.

Is this a bug? I am not sure, it seems that everything is doing what  
is expected of it, just that in this case that "correct" behavior is  
producing surprising results. While I normally all for minimizing  
surprises, this case is a little out on the edge already. If you  
simply do this in your BUILD method:

     my $meta = $self->meta->create_anon_class(
	superclasses => [__PACKAGE__],
	methods      => $methods,
     );
     $meta->rebless_instance($self);
     $meta->add_method('meta' => sub { $meta });

Everything just works as expected, since the $meta is now closed over  
and therefore is not garbage collected. I would not want this to be  
the default behavior though since this would break the garbage  
collection of the anon-classes. We have a special case when you  
create an instance using an anon class that will stash the $meta to  
assure it won't go out of scope until the instance itself goes out of  
scope. Perhaps the best solution is to do the same with  
rebless_instance as well. I am going to look into it right now.

- Stevan


On Mar 27, 2008, at 11:39 PM, Christopher Brown wrote:
> Stevan,
>
> Thanks for the quick reply.  Please find a test case that fails.
>
> System: Linux 2.6.20-2931.fc7 #1 SMP Mon Aug 13 10:12:37 EDT 2007  
> i686 i686 i386 GNU/Linux
> Perl: v5.10.0 built for i686-linux-thread-multi-64int-ld
>
> I think that what is most interesting is that the meta->add_method  
> does not report that it is failing, so I suspect that the method is  
> installed somewhere, but I don't know where or how to find it.
>
> I think that the ability to create instance based attribute is  
> powerful and common and useful in all sorts of situation.  If I  
> could have a vote I would suggest that it be easier to create and  
> instance class.  The create_anon_class()->rebless( $self ) is not  
> the prettiest syntax.  I think that it would be useful to have a  
> standard mechanism to accomplish this.
>
> $self->make_instance_class;
> $self->meta->make_instance_class;
>
> Or something like that.
>
> ( I have been taken notes as I learn Moose over the past several  
> weeks.  If you would like me to share my thoughts, I would be happy  
> to send them to you privately or the list. In no way, should they  
> be taken as harsh criticism.  I still think that Moose is the best  
> thing to happen to Perl in a long time. )
>
> Best Wishes,
>
> Chris
>
>
>
> __TEST CASE:__
>
> use feature 'say';
>
> # PACKAGE: person_1
> #   simple object with two attributes
> package person_1;
> use Moose;
>
>     has name => (
>         is      => 'ro'     ,
>         isa     => 'Str'    ,
>         required=> 1
>     );
>
>     has occupation => (
>         is      => 'rw'     ,
>         isa     => 'Str'    ,
>         required=> 0        ,
>     );
>
>
> # PACKAGE: person_2
> #   same as person_1 except it also has a BUILD method that
> #   dynamically installs a method 'is_cool' based on occupation
> #   type.  ( I know this can be a lazy attribute.  This is just
> #   for illustration purposes.  The lazy attribute doesn't work
> #   when you want instance based methods though. )
> package person_2;
> use Moose;
>
>     has name => (
>         is      => 'ro'     ,
>         isa     => 'Str'    ,
>         required=> 1
>     );
>
>     has occupation => (
>         is      => 'rw'     ,
>         isa     => 'Str'    ,
>         required=> 0        ,
>     );
>
> sub BUILD {
>
>     my $self = shift;
>
>     my $methods = {};
>
>     if ( $self->occupation eq 'Moose Programmer' ) {
>
>         $methods->{is_cool} =
>             sub {  say $_[0]->name . " is really cool."; } ;
>
>     } else {
>
>         $methods->{is_cool} =
>             sub { say $_[0]->name . " is only kinda cool.\n" } ;
>
>     }
>
>     $self->meta->create_anon_class(
>                    superclasses => [__PACKAGE__],
>                    methods      => $methods,
>     )->rebless_instance($self);
>
> }
>
> # ---------------------------------------------------------
> # PACKAGE: main
> #
> package main;
> my $steve =  person_1->new( {
>     name        => 'Stevan'             ,
>     occupation  => 'Moose Programmer'   ,
> } );
>
>
> my $chris = person_2->new( {
>     name        => 'Chris'              ,
>     occupation  => 'Regular Programmer' ,
> } );
>
>
> # Let's now add a method to Steve
> $steve->meta->add_method( "is_fun", sub { say $_[1]->name . " is  
> fun"; } );
> $steve->is_fun;  # Stevan is fun.
>
> # Let's try the same thing with Chris.  Remember Chris has been  
> reblessed
> $chris->meta->add_method( "is_fun", sub { say $_[0]->name . " is  
> fun"; } );
> $chris->is_fun;  # FAIL!
>
> # Can't locate object method "is_fun" via package  
> "Class::MOP::Class::__ANON__::SERIAL::2" (perhaps you forgot to  
> load "Class::MOP::Class::__ANON__::SERIAL::2"?) at test.pl line 90.
>
>
>
> On Thu, Mar 27, 2008 at 6:47 PM, Stevan Little  
> <[email protected]> wrote:
> Christopher,
>
> I am not sure I am understanding the error you are describing.  
> Could you write up a small failing test for me?
>
> Thanks,
>
> - Stevan
>
>
> On Mar 27, 2008, at 3:09 PM, Christopher Brown wrote:
> Paul and Stevan,
>
> Sorry for the late reply.  Thanks for both answers.  Stevan, I am rely
> impressed with the rapidity of which you and the other Moose members
> respond.  I am also impressed with you presenting a working  
> prototype of my
> problem in as little time and code as you did.  It is really a  
> testament to
> Moose's power.
>
> In the end, I went with the solution that Paul offered.  That is, *
> create_anon_class()->rebless_instance*.   And, I have used this  
> recipe  a
> half-dozen or times since.   It is really powerful technique for  
> installing
> instance methods.  I did run into a snag with reblessing  
> instances.  It
> seems that I cannot access the meta object after reblessing.   I am  
> not so
> sure why I have the following work on the reblessed object:
>
>   $self->meta->add_method( 'test', sub { say "Hello World" } );     
> # $self
> is the reblessed self.
>
> Interestingly enough, the method does not produce an error.
>
> As I am discovering with Moose, there is probably a real simple and  
> elegant
> solutions of which has eluded me.  As always, your help is greatly
> appreciated.
>
> Best,
>
> Chris
>
>
>
>
>
>
> On Thu, Mar 20, 2008 at 8:22 AM, Paul Driver <[email protected]>
> wrote:
>
> Chris,
>
> If you want to add generated methods to the class, it's
> __PACKAGE__->meta->add_method(foobar => sub { print "Foo, bar! 
> \n" });  I
> don't think you want to do this in BUILD though - just at class  
> compile
> time.  I do this a lot when I have a bunch of similar methods and  
> don't want
> to repeat myself.
>
> If you want to add methods to a particular instance ONLY, the thing  
> to do
> is to create an anonymous subclass and rebless your instance to that
> subclass, and add the methods to the subclass.
>
> Some example code - at BUILD time, add a method named $word that  
> does say
> $word for every word passed into the constructor.
>
> ---
>
> use feature q(say);
> package Speaker;
> use Moose;
>
> has words => (
>       is => 'ro',
>       isa => 'ArrayRef[Str]',
>       predicate => 'has_words',
> );
>
> # All Speakers know how to say bar
> sub bar {
>       say 'bar';
> }
>
> sub BUILD {
>       my ($self, $args) = @_;
>       if ($self->has_words) {
>               my %methods = map {
>                       my $word = $_;
>                       ($word => sub { say $word})
>               } @{$self->words};
>
>               $self->meta->create_anon_class(
>                       superclasses => [__PACKAGE__],
>                       methods      => \%methods,
>               )->rebless_instance($self);
>       }
> }
>
> package main;
> my $a = Speaker->new;
> my $b = Speaker->new(words => [qw(baz qux)]);
> $a->bar; # => "Bar"
> $b->bar; # => "Bar"
> $b->qux; # => "Qux"
> say "A" if $a->isa('Speaker'); # => "A"
> say "B" if $b->isa('Speaker'); # => "B"
> $a->qux; # Can't locate method "qux" via class "Speaker"
>
> ----
>
>
> On Mar 19, 2008, at 4:22 PM, Christopher Brown wrote:
>
>  Dave,
>
> Sorry for taking so long to thank you for your reply.  Thanks.  On
> further
> t, it was a silly question, using a plain old method is obviously the
> solution.
>
> I do have follow-up questions, though.  Suppose I want to generate  
> those
> plain old methods dynamically.  Is there a simple, moose way to do  
> this
> in
> the BUILD subroutine.   On e way to accomplish this is to assign an
> anonymous subroutine to a glob.  Like this:
>
> package My::App;
>
> use Moose;
> has name => ( is => 'rw', isa=>'Str' );
>
> sub BUILD {
>       **__PACKAGE__::method = sub { print "hello world\n"; } ;*
> }
>
> package main;
> my $app = My::App->new( { name => 'Moose' } );
> $app->method;
>
>
> But I don't want to do that.  I want to do it the Moose way.
>
> I can hear you ask, but why would you want to do that?  Well  
> suppose, I
> create an object that wraps a query.  If I can iterate over the record
> set I
> can replace the data slot with the new record and still named methods
> that
> act as accessors to the record.  This beats the snot out of trying to
> instanciate a row object for each record.
>
> Thanks in Advance,
>
> Chris
>
>
>
>
>
> On Wed, Mar 12, 2008 at 2:41 PM, Dave Rolsky <[email protected]> wrote:
>
>  On Wed, 12 Mar 2008, Christopher Brown wrote:
>
>  Despite the 'lazy' evaluation, it appears as if the attribute value
> is
>
> being
>
> cached instead of being re-evaluated when the accessor is being
> called.
>
>
> Lazy just means "don't generate the attribute from the default sub
> until
> the _first_ time it is requested". It's still cached.
>
> If you want to return a different value each time then you just want a
> plain old method, not an attribute.
>
>
> -dave
>
> /*==========================
> VegGuide.Org
> Your guide to all that's veg
> ==========================*/
>
>
>
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.