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
> ==========================*/
>
>
>
>
>