Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Possible issue with Moose ScalarRef

by greengaroo (Hermit)
on Jan 10, 2013 at 17:54 UTC ( #1012711=perlquestion: print w/ replies, xml ) Need Help??
greengaroo has asked for the wisdom of the Perl Monks concerning the following question:

Happy new year fellow Monks!

I am either doing something wrong of I just found a bug in Moose. I hope its me and not Moose!

What I am trying to do is to save a reference to a scalar in another object so if I change the value of the original scalar in my first object, it will be effective also in the second object.

About this syntax, keep in mind I am using MooseX::Declare, also in this case the "debug_level" in $self isa Int and the "debug_level" in the $obj (Something class) it isa ScalarRef[Int]. My code roughly look like this:

$self->debug_level( 4 ); print "Original: ", \$self->debug_level, " Value: ", $self->debug_ +level, "\n"; my $obj = Something->new( { 'debug_level' => \$self->debug_level, } ); print "Reference: ", $obj->debug_level, " Value: ", ${$obj->debug_ +level}, "\n"; $self->debug_level( 0 ); print "After change:\n"; print "Original: ", \$self->debug_level, " Value: ", $self->debug_ +level, "\n"; print "Reference: ", $obj->debug_level, " Value: ", ${$obj->debug_ +level}, "\n";

And the output is:

Original: SCALAR(0xd463da0) Value: 4 Reference: SCALAR(0xd468640) Value: 4 After change: Original: SCALAR(0xd463da0) Value: 0 Reference: SCALAR(0xd468640) Value: 4

Why is the SCALAR address not the same? And of course, that leads to the problem of the second instance not being updated!

Here is the definition of the attribute in the first class (it extends MooseX::App::Cmd::Command):

has 'debug_level' => ( traits => [qw(Getopt)], is => 'rw', isa => 'Int', default => 0, cmd_aliases => 'd', documentation => 'Debug mode: 0-4; Default: 0;' );

Here is the definition of the attribute in the second class (it uses MooseX::Declare):

has 'debug_level' => ( is => 'rw', isa => 'ScalarRef[Int]', );

What am I doing wrong??? Thank you all!

UPDATE: I found a workaround, and it seems to do what I want, but I still have a different SCALAR address for the original and the reference. I now do this:

my $obj = Something->new( { # Before: #'debug_level' => \$self->debug_level, # Now: 'debug_level' => \$self->{'debug_level'}, } );
Testing never proves the absence of faults, it only shows their presence.

Comment on Possible issue with Moose ScalarRef
Select or Download Code
Re: Possible issue with Moose ScalarRef
by tobyink (Abbot) on Jan 10, 2013 at 20:23 UTC

    I agree; this seems broken. Small test case...

    use strict; use warnings; use Test::More; { package Thing; use Moose; has attr => (is => 'ro'); } { package ThingWithRef; use Moose; has attr => (is => 'ro'); sub translate_to_dutch { my $self = shift; ${ $self->attr } =~ s/e/a/; } } my $obj1 = Thing->new(attr => 'Hello'); my $obj2 = ThingWithRef->new(attr => \$obj1->attr); $obj2->translate_to_dutch; is($obj1->attr, 'Hallo'); done_testing;

    If you use Moo instead of Moose, then things work as expected.

    UPDATE: OK, not a bug. It's just that the reference \$obj1->attr is not guaranteed to be a reference to the slot within the object. So changing data via the reference will not necessarily effect $obj1. If it works, it works; if not, then it won't; no guarantees. Currently it works in Moo but not Moose, but the situation - in either - could change.

    If you want what you want (for both objects to have access to the same piece of data) then you need it to be a scalar ref in both classes.

    UPDATE 2: It also fails in Moo if Moo is in pure Perl mode (not XS).

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      Thanks for all your tests! It means I'm not crazy! But that is a very strange behavior!

      You mentioned "changing data via the reference", I want to do the opposite in fact, changing the data in the original variable so I can reference to it from another object. I guess the result is the same.

      Anyway, I found a workaround, and it seems to do what I want, but I still have a different SCALAR address for the original and the reference. I now do this:

      my $obj = Something->new( { # Before: #'debug_level' => \$self->debug_level, # Now: 'debug_level' => \$self->{'debug_level'}, } );

      Do you think it would always work, or is it "random" like the previous way?

      Testing never proves the absence of faults, it only shows their presence.

        It will work for objects which are internally implemented as hashrefs. Moose does this by default, and it's quite a bit of work to persuade it to implement objects any other way.

        (There are various MooseX modules on CPAN that can do that work for you, including MooseX::InsideOut and my own MooseX::ArrayRef which allows you to create Moose classes based on arrayrefs.)

        It is generally considered poor style to peek at the hashref internals of Moose objects (indeed, blessed objects in general). One good reason is that if you're relying on a module's internals, it gives the module's author less freedom to refactor those internals.

        In this case, when you appear to be the author of both modules, so the above might not be a concern, the strong argument against poking the hashref is that it bypasses type constraint checks.

        One alternative solution might be to pass a closure from your first class to your second class...

        use strict; use warnings; use Test::More; { package Thing; use Moose; has attr => (is => 'rw'); sub get_closure { my $self = shift; return sub { $self->attr(@_) }; } } { package ThingWithRef; use Moose; has closure => (is => 'ro'); sub translate_to_dutch { my $self = shift; my $str = $self->closure->(); $str =~ s/e/a/; $self->closure->($str); } } my $obj1 = Thing->new(attr => 'Hello'); my $obj2 = ThingWithRef->new(closure => $obj1->get_closure); $obj2->translate_to_dutch; is($obj1->attr, 'Hallo'); done_testing;
        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (12)
As of 2014-07-31 19:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (251 votes), past polls