Passing function references in the constructor

K

koszalekopalek

Hello,

I would like to create a number of objects of the same class (mypack)
and specify a reference to a function in the constructor. I would like
to
let the module user either use a predefined function (e.g. test1)
provided in the class or let him write his own function and provide
a reference to it in the constructor.

The example below does just that.

I create object $obj1 of class mypack and use a predefined sub test1.
Then I create $obj2 and pass a reference to a custom function
customtest.

The problem is that the two functions
$obj1->riddle (1, 2, 3);
and
$obj2->riddle (1, 2, 3);
receive different arguments depending on where they come from.
The first argument passed to the function test1 is the name of the
class (and then comes the number, i.e. 1). If it is customtest, it
does
not get the name of the class.


This is the output of the attached program:
---- test1 args ----
mypack
1
---- customtest args ----
1
2

What would be the most elegant way to tackle this problem? I'd
rather
not shift arguments in test1 or do anything like that. Maybe there
is
a better way to pass function references in the constructor?


#!/usr/bin/perl

{
package mypack;

sub new {
my $class = shift;
$self = { @_ };
bless $self, $class;
return $self;
};

sub riddle {
my $self = shift;
my $testref = \&test;
my $testref = $self->{fun};
&$testref (@_);
};


sub test1 {
print "---- test1 args ----\n";
print @_[0], "\n";
print @_[1], "\n";
};



};


sub customtest {
print "---- customtest args ----\n";
print @_[0], "\n";
print @_[1], "\n";
}


my $obj1 = mypack->new(
fun => sub { mypack->test1 (@_) }
);
$obj1->riddle (1, 2, 3);


my $obj2 = mypack->new(
fun => \&customtest
);
$obj2->riddle (1, 2, 3);
 
M

Michele Dondi

I would like to create a number of objects of the same class (mypack)
and specify a reference to a function in the constructor. I would like
to
let the module user either use a predefined function (e.g. test1)
provided in the class or let him write his own function and provide
a reference to it in the constructor.

So far, so fine: just use something like

my ($class, $code)=@_;
# ...
$code=\&test1 unless 'CODE' eq ref $code;
The problem is that the two functions
$obj1->riddle (1, 2, 3);
and
$obj2->riddle (1, 2, 3);

These are not functions, but methods.
receive different arguments depending on where they come from.
?!?

The first argument passed to the function test1 is the name of the
class (and then comes the number, i.e. 1). If it is customtest, it
does

It depends on how you're calling it. Do you expect it to be called as
a plain sub or as a method? Just take the decision first, make that an
API and stick with it.
What would be the most elegant way to tackle this problem? I'd
rather
not shift arguments in test1 or do anything like that. Maybe there

Why not? It just depends, again, on how you intend to call it.
sub new {
my $class = shift;
$self = { @_ };
bless $self, $class;
return $self;
};

The constructor doesn't seem to handle a subref passed to it in any
particular way. Hence no sign of the defaulting to test1() which you
hinted to.
sub riddle {
my $self = shift;
my $testref = \&test;
my $testref = $self->{fun};

Huh?!? Under warnings this will also issue a... well, warning! Why the
double assignment anyway?
&$testref (@_);

Whatever, if $testref contains a subref (but then you should really
check somewhere) and if you call it like that, or as in

$testref->(@_); # preferred!

then you have a normal sub call, i.e. no class name or object passed
in as the first argument. Alternatively, you can do:

$self->$testref(@_);

and then it will be called like a method on $self. Whether you want
this, or the other way round as I said is something you must choose in
advance, and then stay consistent.
my $obj1 = mypack->new(
fun => sub { mypack->test1 (@_) }
);

Huh?!? You're passing in an anonymous sun which *in its body* (hence
the problem has NOTHING to do with "passing function references in the
constructor"!) will call your package's test1() as a *method*. You can
use the fully referenced name instead, if you really want to do so:

fun => sub { mypack::test1 (@_) }

But you probably meant just

fun => \&mypack::test1

All in all, the simplest variation of your original code (also fixing
some stylistic issues, what's with all those semicolons?) that still
does no (good) checks, etc. is:

#!/usr/bin/perl

use warnings;
use strict;

{
package Mypack; # all lowercase ones are reserved for pragmatas

sub new {
my $class = shift;
bless { @_ }, $class;
}

sub riddle {
my $self = shift;
($self->{fun} || \&test1)->(@_);
}

sub test1 { print "test1 args: [@_]\n" }
}

my $obj1 = Mypack->new; # defaulting to test1
$obj1->riddle(1, 2, 3);

my $obj2 = Mypack->new( fun => sub { print "customtest args: [@_]\n"
} );
$obj2->riddle(1, 2, 3);

__END__


Alternatively, make riddle() into

sub riddle {
my $self = shift;
my $code = $self->{fun} || \&test1;
$self->$code(@_);
}

And see what happens. For the last time: just choose what you like
best.

After much thinking... I suspect smell of XY problem here. See e.g.
<http://perlmonks.org/?node=XY+problem>.


Michele
 
M

Mumia W.

Hello,

I would like to create a number of objects of the same class (mypack)
and specify a reference to a function in the constructor. I would like
to
let the module user either use a predefined function (e.g. test1)
provided in the class or let him write his own function and provide
a reference to it in the constructor.

The example below does just that.

I create object $obj1 of class mypack and use a predefined sub test1.
Then I create $obj2 and pass a reference to a custom function
customtest.

The problem is that the two functions
$obj1->riddle (1, 2, 3);
and
$obj2->riddle (1, 2, 3);
receive different arguments depending on where they come from.
The first argument passed to the function test1 is the name of the
class (and then comes the number, i.e. 1). If it is customtest, it
does
not get the name of the class.


This is the output of the attached program:
---- test1 args ----
mypack
1
---- customtest args ----
1
2

What would be the most elegant way to tackle this problem? I'd
rather
not shift arguments in test1 or do anything like that. Maybe there
is
a better way to pass function references in the constructor?


#!/usr/bin/perl

These lines are missing:

use strict;
use warnings;

Modifying your program to work under these conditions will help you
catch many errors.
{
package mypack;

sub new {
my $class = shift;
$self = { @_ };
bless $self, $class;
return $self;
};

sub riddle {
my $self = shift;
my $testref = \&test;
my $testref = $self->{fun};
&$testref (@_);
};


sub test1 {
print "---- test1 args ----\n";
print @_[0], "\n";
print @_[1], "\n";
};

Since you don't do "my $self = shift;", test is not expected to be a
method, but a normal function--which is okay.
};


sub customtest {
print "---- customtest args ----\n";
print @_[0], "\n";
print @_[1], "\n";
}


my $obj1 = mypack->new(
fun => sub { mypack->test1 (@_) }
);

But here, you invoke 'test' as a class method of the 'mypack' package.
Change that line to the following, and you'll get better results:

fun => sub { test1(@_) }

Or try this:

fun => \&mypack::test1

$obj1->riddle (1, 2, 3);


my $obj2 = mypack->new(
fun => \&customtest
);
$obj2->riddle (1, 2, 3);

There are several more problems with your program. Let strictures (use
strict) and warnings (use warnings) tell you about them.

Just by fixing the problems reported by "use strict;" and "use
warnings;", I was able to create an improved version of your program:

#!/usr/bin/perl
use strict;
use warnings;

package Mypack;

sub new {
my $class = shift;
my $self = { @_ };
bless $self, $class;
return $self;
}

sub riddle {
my $self = shift;
my $testref = \&test1;
if ($self->{fun}) { $testref = $self->{fun}; }
&$testref (@_);
}

sub test1 {
print "---- test1 args ----\n";
print $_[0], "\n";
print $_[1], "\n";
}


package main;

sub customtest {
print "---- customtest args ----\n";
print $_[0], "\n";
print $_[1], "\n";
}

my $obj1 = Mypack->new();

my $obj2 = Mypack->new(
fun => \&customtest
);

$obj1->riddle(1, 2, 3);
$obj2->riddle(1, 2, 3);
 
K

koszalekopalek

(...)
But you probably meant just

fun => \&mypack::test1


Thanks to everybody that answered.

Now I understand better what's going on.
I think replacing
fun => sub { mypack::test1 (@_) }
with
fun => \&mypack::test1
is what I was looking for. (I tried that with
the arrow syntax
fun \&mypack->test1
but that gave me the following error:
Backslash found where operator expected at a.pl line 46, near "fun \"

I always (well, almost) use strict - the example I
posted was a chopped down version of what I was
trying to achieve.

After much thinking... I suspect smell of XY problem here. See e.g.
<http://perlmonks.org/?node=XY+problem>.

Well, that may be true - I think I have some problems
figuring out what should be enclosed in the class and
what should not.

Thanks again,

K.O.

Michele
--
{$_=pack'B8'x25,unpack'A8'x32,$a^=sub{pop^pop}->(map substr
(($a||=join'',map--$|x$_,(unpack'w',unpack'u','G^<R<Y]*YB='
.'KYU;*EVH[.FHF2W+#"\Z*5TI/ER<Z`S(G.DZZ9OX0Z')=~/./g)x2,$_,
256),7,249);s/[^\w,]/ /g;$ \=/^J/?$/:"\r";print,redo}#JAPH,
 
M

Michele Dondi

I think replacing
fun => sub { mypack::test1 (@_) }
with
fun => \&mypack::test1

In fact with that bacslash in front, &mypack::test1 is fundamentally
nothing but a name, which means you're taking a reference to the sub
it refers to.
is what I was looking for. (I tried that with
the arrow syntax
fun \&mypack->test1
but that gave me the following error:
Backslash found where operator expected at a.pl line 46, near "fun \"

Missing C< => >?


Michele
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top