in reply to Avoiding circular references

I'm not really sure what the question is, but if your coderef doesn't need access to $self and only needs access to $dbh, then yes, it's a good idea for it to avoid closing over $self.

The case where you'd want it to close over $self instead of $dbh would be if there was something like a $self->switch_database method that set a new database to connect to, and you wanted your coderef to be aware of the switch instead of continuing to point to the old database.

Replies are listed 'Best First'.
Re^2: Avoiding circular references
by ikegami (Patriarch) on Dec 06, 2019 at 22:14 UTC

    it's a good idea for it to avoid closing over $self.

    [I didn't study the OP's code, so this may or may not be pertinent, but I think it is.]

    When you have an object that calls a callback, it's a good idea to pass the object to the callback as an argument.

    For example, say there's a class that calls a function periodically.

    The following exhibits a circular reference:

    my $timer; $timer = Timer->new( callback => sub { ... if (...) { $timer->cancel; } ... }, period => 5, )

    The following doesn't exhibit a circular reference:

    my $timer = Timer->new( callback => sub { my ($timer) = @_; ... if (...) { $timer->cancel; } ... }, period => 5, )

    The latter requires that Timer passes a reference to itself to the callback.

      Your problem can be solved using something similar to the aforementioned "trick", as shown below.

      I've identified the three changes I've made using "# <-----".

      use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { #$self->this_is_from_A(@_) shift->this_is_from_A(@_) # <----- }, ); bless $self, $class; } sub do_something { my $self = shift; #$self->{helper}->called_from_helper(); $self->{helper}->called_from_helper($self); # <----- } sub this_is_from_A { my ( $self, $arg ) = @_; print "$arg\nthis_is_from_A using $self->{dbi}\n"; } package Helper; sub new { my $class = shift; my %def = ( a => "something" ); my %arg = ( %def, ref $_[0] eq "HASH" ? %{ $_[0] } : @_ ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; #$self->{helper_function}->("param from Helper"); $self->{helper_function}->(@_, "param from Helper"); # <----- } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);

        In this case, however, it's easier to use weaken.

        Simply change

        sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { $self->this_is_from_A(@_) }, ); bless $self, $class; }
        to
        use Scalar::Util qw( weaken ); sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; { weaken( my $self = $self ); $self->{helper} = Helper->new( helper_function => sub { $self->this_is_from_A(@_) }, ); } bless $self, $class; }

        (By using the same name for the weakened variable as the unweakened one, we avoid using the wrong one by accident.)

Re^2: Avoiding circular references
by frazap (Monk) on Dec 06, 2019 at 13:54 UTC
    I would have someting like this working
    use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { my $dbi = $$href{dbi}; return $self->this_is_from_A(@_); } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ($self, $arg) = @_; print "$arg\nthis_is_from_A using $dbi\n"; } package Helper; sub new { my $class = shift; my %def = ( a => "something"); my %arg = ( ref $_[0] eq "HASH" ? ( %def, %{ $_[0] } ) : ( %def, + @_ ) ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; my $coderef = $self->{helper_function}->(); $coderef->("param from Helper"); } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);
    But this does not even compile since $dbi in this_is_from_A is not declared.

      So you pass $dbi as an argument.

      package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { my $dbi = $$href{dbi}; return $self->this_is_from_A($dbi, @_); # p +ass $dbi } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ($self, $dbi, $arg) = @_; # r +eceive $dbi print "$arg\nthis_is_from_A using $dbi\n"; }

        Thanks !

        but that still make a circular reference because of $self

        This works

        use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { &this_is_from_A( $$href{dbi}, @_) } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ($dbi, $arg) = @_; print "$arg\nthis_is_from_A using $dbi\n"; } package Helper; sub new { my $class = shift; my %def = ( a => "something"); my %arg = ( ref $_[0] eq "HASH" ? ( %def, %{ $_[0] } ) : ( %def, + @_ ) ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; $self->{helper_function}->("param from Helper"); } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);