dd1942 has asked for the wisdom of the Perl Monks concerning the following question:

Hello Experts, I'm trying to share intance via Moose::Role like below

#### Role #### package MyRole; use Moose::Role; has 'nodes' => ( traits => ['Hash'], is => 'rw', isa => 'HashRef[Item]', handles => { set_node => 'set', get_node => 'get', all_nodes => 'keys', } ); 1; #### B #### package BBB; use Moose; with 'MyRole'; sub TIEHASH { my $class = shift; my $self = bless {},$class; while(my($key,$value) = each(%ENV)) { $self->set_node($key => $value); } return $self; } sub FETCH { my $self = shift; my $param = shift; return $self->get_node($param); return undef; } sub STORE { my $self = shift; my $key = shift; my $value= shift; return unless defined($key); return $self->set_node($key => $value); } sub DESTORY { return 1 } 1; no Moose; __PACKAGE__->meta->make_immutable; #### A #### package A; use Moose; use base 'BBB'; with 'MyRole'; sub bind_vars { my $self = shift; my $instance = tie(%ENV,'BBB'); $self->BBB($instance); return 1; } has 'BBB' => ( is => 'rw', ); 1; no Moose; __PACKAGE__->meta->make_immutable; #### main #### package main; use base 'A'; my $obj = A->new; $obj->bind_vars; # This works , but that's not I expected print $obj->BBB->get_node('TERM'),"\n"; # I wish to use it in this way print $obj->get_node('TERM'),"\n"; $ENV{TEST} = 123; print $obj->BBB->get_node('TEST'),"\n"; print $obj->get_node('TEST'),"\n"

As you can see, this is my requirements:

1.I want to directly call A->get_node(), other than A->BBB->get_node();

2.I don't want to import DESTROY/FETCH,etc into A, I wish to use it as a delegation in somehow.

May I ask if there is any features / easier way can help me directly call B.get_node transparently in A?

Your any kind of help is appreciated!

Replies are listed 'Best First'.
Re: Can I share instance through Moose::Role ?
by tobyink (Canon) on Feb 10, 2014 at 10:53 UTC

    This part seems wrong because you're bypassing Moose's constructor. (And constructors are one of the key selling points for using Moose.)

    sub TIEHASH { my $class = shift; my $self = bless {},$class; while(my($key,$value) = each(%ENV)) { $self->set_node($key => $value); } return $self; }

    Instead, I'd write that as:

    sub TIEHASH { my $class = shift; my $self = $class->new; while(my($key,$value) = each(%ENV)) { $self->set_node($key => $value); } return $self; }

    So TIEHASH just becomes a wrapper for the Moose constructor.

    As well as constructors, Moose also provides destructors, so you shouldn't be overriding the DESTROY method that Moose provides for you. Luckily, you've misspelt it as DESTORY, so no harm done.

    To answer your question, this is how package A should be written:

    package A; use Moose; has BBB => ( is => 'rw', lazy => 1, builder => '_build_BBB', handles => [qw( get_node )], ); sub _build_BBB { my $self = shift; my $instance = tie(%ENV, 'BBB'); return $instance; }

    And this is how you'd use it:

    my $a = A->new; print $a->get_node('TERM'), "\n";

    Note that I've ditched your bind_vars method in favour of using a lazy builder for the BBB attribute. When the value of BBB is needed, Moose will automatically run the _build_BBB method to populate it. This means that code outside the A class doesn't need to remember to manually call the bind_vars method in order to bring the object into a usable state.

    That said, I'm not sure why you're mucking around with trying to tie %ENV anyway. Nothing you've shown demonstrates a need for tying. This seems far simpler:

    use v5.14; package MyRole { use Moose::Role; has nodes => ( traits => ['Hash'], is => 'rw', isa => 'HashRef[Item]', handles => { set_node => 'set', get_node => 'get', all_nodes => 'keys', } ); } package BBB { use Moose; with 'MyRole'; } package A { use Moose; has BBB => ( is => 'rw', lazy => 1, default => sub { BBB->new(nodes => \%ENV) }, handles => [qw( get_node )], ); } my $a = A->new; say $a->get_node('TERM');
    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      Hello tobyink,

      Thanks for your reply, your solution is simple and elegance. Regarding "tie" function, I was tried to track other scripts that may change %ENV in that way, and now it seems much simple to do this in modifiers. :)

        When you write \%ENV you get a "live" reference to the hash %ENV. Any further changes to %ENV will be reflected in \%ENV.

        Example:

        $ENV{FOO} = 1; my $ref = \%ENV; # Change something in %ENV $ENV{FOO} = 2; print $ref->{FOO}, "\n"; # prints 2.

        If you actually need to check other scripts changing %ENV, you're out of luck. That's not how Unix environment variables work. Each process gets its own copy of the environment, cloned from its parent process. Any changes it makes to its environment cannot be seen by its parent process, nor its sibling processes. Its child processes will of course see the changes in their own cloned copies of the environment, but they cannot pass back any of their own changes.

        If you actually need to communicate changes to %ENV between processes, then you need to look into interprocess communication (IPC). This is usually accomplished by reading from and writing to Unix sockets (which are essentially filehandles used for communication between processes on the same machine) or TCP sockets (which are Unix sockets generalized to work over the Internet).

        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: Can I share instance through Moose::Role ?
by choroba (Cardinal) on Feb 10, 2014 at 08:13 UTC
    Please, post a code that demonstrates the A->B->a() way of calling. Test the code before posting: there already is a module called B in Perl.
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Hello choroba,

      I apologize for the untested code. And thank you for your reply.

      I've update my questions and the code. And I also found a workaround , although it is not "that" perfect.

      Just need copy the MyRole.nodes reference from consumer 'B' into consumer 'A'.

      Anyhow, currently it works for me, but still glad to see more better/safer solution that possibly don't need any copy?

      sub bind_vars { my $self = shift; my $instance = tie(%ENV,'BBB'); $self->BBB($instance); $self->nodes($instance->nodes); return 1; }