est
est

Reputation: 577

Passing code reference to external module

I use an external module (say Foo.pm) that I don't have control over. The way to use it is like below, which works fine:

use Foo ();  

my %config = (
    MODE  => 'NORMAL',
    ERROR => \&my_error,    # error handling routine
);

Foo::init(%config);

sub my_error {
    my ($message) = @_;
    ...
}

However I'm having trouble to pass in my_error() to the external module when I'm writing in OO style as the first parameter to my_error() is now $self:

package MyPackage;
use Foo ();

sub new {
    my $self = bless {
        environment => 'TEST',
        config      => {
            MODE  => 'NORMAL',
            ERROR => \&my_error,   # WRONG ??!
        },
    }, __PACKAGE__;

    Foo::init( %{$self->{config}} );
}

sub my_error {
    my ($self, $message) = @_;
    ...
}

How do I solve it? Passing &{ $self->my_error } does not seem to work.

Thanks!

Upvotes: 1

Views: 73

Answers (2)

simbabque
simbabque

Reputation: 54323

A good alternative to the final part of ikegami's excellent and detailed answer is to use curry::weak.

use curry::weak;

my $self = bless({}, $class);
%$self = (
    environment => 'TEST',
    config      => {
        MODE  => 'NORMAL',
        ERROR => $self->curry::weak::my_error(),
    },
);

mst, the author of curry, gives a reasonably understandble explanation of how that works in this lightning talk.

Upvotes: 4

ikegami
ikegami

Reputation: 385655

If you need a sub when you don't have one, you need to make one. You can make an anonymous one.

sub { $self->my_error(@_) }

So that means

my $self = bless {
    environment => 'TEST',
    config      => {
        MODE  => 'NORMAL',
        ERROR => sub { $self->my_error(@_) },
    },
}, $class;

But there are complications. In your code, $self hasn't been declared yet when you try to capture it. Fix:

my $self = bless({}, $class);
%$self = (
    environment => 'TEST',
    config      => {
        MODE  => 'NORMAL',
        ERROR => sub { $self->my_error(@_) },
    },
);

But that creates a memory leak. The sub captures $self, which references a hash that contains a reference to the sub. Fix:

use Scalar::Util qw( weaken );

my $self = bless({}, $class);
{
    weaken( my $self = $self );
    %$self = (
        environment => 'TEST',
        config      => {
            MODE  => 'NORMAL',
            ERROR => sub { $self->my_error(@_) },
        },
    );
}

As simbabque points out, the curry::weak module can simplify(?) this a little.

use curry::weak qw( );

my $self = bless({}, $class);
%$self = (
    environment => 'TEST',
    config      => {
        MODE  => 'NORMAL',
        ERROR => $self->curry::weak::my_error(),
    },
);

But I think it'll just add confusion.

Upvotes: 7

Related Questions