http://qs321.pair.com?node_id=376160

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

I'm trying to write some modules that use Net::EasyTCP. There is a parent class that gets extended by several subclasses. The problem I'm running in to is with this code from EasyTCP:
$server->setcallback( data => \&gotdata, connect => \&connected, disconnect => \&disconnected, ) || die "Error setting callbacks: $@\n";
Before I used the OO-approach, I had no problem with this, but now I can't figure out how to do coderefs for OO versions of &gotdata, &connected, and &disconnected. The below is what I'd like to do, though, obviously the syntax is wrong. Is this do-able?
package ParentClass; # [...] sub init_server { my $self = shift; # [...] $self->{'server'}->setcallback( data => \&gotdata, # this coderef is wrong connect => \&connected, # and this disconnect => \&disconnected, # this too ) || die "Error setting callbacks: $@\n"; # [...] } sub gotdata { my $self = shift; # [...] } package ChildClass; use ParentClass; @ISA = qw(ParentClass); sub gotdata { my $self = shift; # [...] }

Replies are listed 'Best First'.
Re: coderef to an object method
by revdiablo (Prior) on Jul 21, 2004 at 05:29 UTC

    You're taking the coderef fine, but you don't show us how you're using it. To use a coderef as a method, you need to can manually pass the object as the first arg. Here's an example:

    { package foo; sub new { bless {}, shift } sub foo { ++$_[0]{foo} } } $foo = foo->new; $cr = \&foo::foo; print $cr->($foo), "\n" for 1 .. 5;

    Perhaps if you post the code where you try to use the coderef, we can give a less general answer.

    Update: You can also wrap the method call in an anonymous subroutine, if you'd prefer that:

    { package foo; sub new { bless {}, shift } sub foo { ++$_[0]{foo} } } $foo = foo->new; $cr = sub { $foo->foo(@_) }; print $cr->(), "\n" for 1 .. 5;

    Update: fixed to reflect chromatic's reply

      In order to use a coderef as a method, you need to manually pass the object as the first arg.

      Not necessarily.

      #!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; package Foo; sub new { bless {}, $_[0] } sub report { return [ @_ ] } package main; my $foo = Foo->new(); my $report = \&Foo::report; my $moreport = $foo->can( 'report' ); is_deeply( $foo->$report( 'abc' ), [ $foo, 'abc' ], 'coderef should work as method on object' ); is_deeply( $foo->$moreport( 'def' ), [ $foo, 'def' ], '... even if returned from can()' );
        Well, if you call a CODEREF (eg. as returned by ->can()) as object method without passing the objet itself as first argument, you'll lose the object context. So, you'll not be able to access the object properties, nor call any of these methods...
Re: coderef to an object method
by etcshadow (Priest) on Jul 21, 2004 at 05:33 UTC
    Well, first of all... when you do
    data => \&gotodata,
    you're doing essentially the same thing as:
    data => sub { gotodata(@_) },
    (they aren't the same thing, exactly, but they're functionally equivalent for your purposes, one is a named referrence and the other is an anonomous referrence to essentially the same thing). I bother to point that out because then it's easier to see the transition to what you should be doing for method calls, which is this:
    data => sub { $self->gotodata(@_) },
    That is: creating an anonomous subroutine reference for a closure around the method call.
    ------------ :Wq Not an editor command: Wq
      data => sub { $self->gotodata(@_) },
      There is a gotcha in this, in that you have just made your anonymous sub into a closure. Note that $self is in the pad space of the surrounding code, and its value is the one object that is around when the line is executed.

      I have seen memory leaks, where the coderef gets stored into a member of $self's hash (or some structure deeper under $self), leading to a subtle circular reference.

      --
      I'm Not Just Another Perl Hacker

Re: coderef to an object method
by ysth (Canon) on Jul 21, 2004 at 08:52 UTC
    You aren't showing how the callbacks are called. If they are called as methods (i.e.:
    $method = $self->{server}->getcallback("data"); $self->$method();
    ), you can just look up a coderef by method name for an object using can:
    ...setcallback( data => $self->can("gotdata"), connect => $self->can("connected"), disconnect => $self->can("disconnect") )
    If a method isn't available for an object, can will return undef.

    If the callbacks are expected to remember what object they belong (i.e. they are called like &{...getcallback("data")}()), use an anonymous sub:

    ...setcallback( data => sub { $obj->gotdata }, ...
Re: coderef to an object method
by ambrus (Abbot) on Jul 21, 2004 at 09:04 UTC

    When you call methods, it would not be easy to take a code reference, as the function that gets called depends on the class that the object is blessed into. (A module might even do tricks like blessing objects to different classes depending on its constructor options or even rebless it when it changes a state; or it can change the method dynamically with AUTOLOAD or else.)

    Thus, the perl equivalent of a pointer to method is to simply use a string. There's a less known way to call a method inderectly via a string, which is like this:

    use Math::BigRat; $m = "new"; $a = Math::BigRat->$m(5); $b = Math::Big +Rat->$m(8); for $m (qw(badd bsub bmul bdiv)) { $x = $a->copy->$m($b); + print "$a $m $b = $x\n" }

    The problem with this notation is that you can not replace the method name with an arbitary braced expression, so you must use for example

    use Math::BigRat; $m = "new"; $a = Math::BigRat->$m(5); $b = Math::Big +Rat->$m(8); for $m (qw(add sub mul div)) { $x = $a->copy->${\("b".$m) +}($b); print "$a $m $b = $x\n" }
    if you use a more complicated expression than a single variable.

    (Doesn't anyone know where this indirect method syntax is documented in perldoc?)

    Update 2: Ah, it seems that I have misuderstood your question.

    If I understand correctly, you need code references so that you can give it as callbacks. I still say you should not directly take coderefs to a method, as that would break the OO approach. Instead, you need to create anonymous functions that call the method, like this:

    package ParentClass; # [...] sub init_server { my $self = shift; # [...] $self->{'server'}->setcallback( data => sub { $self->gotdata(@_); }, connect => sub { $self->connected(@_); }, disconnect => sub { $self->disconnected(@_), ) || die "Error setting callbacks: $@\n"; # [...] } # [...]

    Or you can factor the subroutine creations out like this:

    sub UNIVERSAL::method { my($self, $name, @rest) = @_; sub { $self->$name(@rest, @_); }; } package ParentClass; # [...] sub init_server { my $self = shift; # [...] $self->{'server'}->setcallback( data => $self->method("gotdata"), connect => $self->method("connected"), # [...]

    Update 3: it seems that etcshadow has already given essentially the same answer.

      When you call methods, it would not be easy to take a code reference

      Sure it is, if the class writer has done his job correctly. Just call can.

Re: coderef to an object method
by Your Mother (Archbishop) on Jul 21, 2004 at 18:54 UTC

    You might also try just using method names instead of coderefs. To me, this is part of the beauty of object syntax.

    %callback = ( data => 'gotdata', connect => 'connected', # ... ); $method = $callback{$whichone}; $object->$method;

    Update: I realized this approach probably has no place with the module you're using.