Passing function references in the constructor

Discussion in 'Perl Misc' started by koszalekopalek@interia.pl, Jun 24, 2007.

  1. Guest

    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);
    , Jun 24, 2007
    #1
    1. Advertising

  2. On Sun, 24 Jun 2007 13:52:35 -0700, wrote:

    >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
    --
    {$_=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,
    Michele Dondi, Jun 24, 2007
    #2
    1. Advertising

  3. Mumia W. Guest

    On 06/24/2007 03:52 PM, wrote:
    > 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);
    Mumia W., Jun 25, 2007
    #3
  4. Guest

    On Jun 25, 12:46 am, Michele Dondi <> wrote:

    (...)
    > 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,
    , Jun 26, 2007
    #4
  5. On Mon, 25 Jun 2007 22:51:47 -0700, wrote:

    >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
    --
    {$_=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,
    Michele Dondi, Jun 26, 2007
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Roger Leigh
    Replies:
    8
    Views:
    436
    Karl Heinz Buchegger
    Nov 17, 2003
  2. Replies:
    3
    Views:
    448
    Victor Bazarov
    Nov 10, 2004
  3. Midas
    Replies:
    2
    Views:
    245
    Midas
    Jan 2, 2004
  4. Generic Usenet Account
    Replies:
    10
    Views:
    2,222
  5. ingoweiss
    Replies:
    4
    Views:
    210
    Julian Turner
    May 12, 2006
Loading...

Share This Page