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

Given the following snippet of code below:

... my $change_mode = \&change_mode; ... #Case 1: Callback routine called directly with anonymous reference $main->Button(-text => 'To Lower Case', -command => sub { &change_mode(1) } )->pack; ... #Case 2: Callback routine called with argument via named reference ve +r. 1 $main->Button(-text => 'To Upper Case', -command => &{$change_mode}(2) )->pack; #Case 3: Callback routine called with argument via named reference ve +r. 2 $main->Button(-text => 'To Mixed Case', -command => $change_mode->(3) )->pack;

Why does case 1, which uses an anonymous reference to the subroutine, work, while case 2 and 3 fail? What is the proper syntax for passing arguments to a subroutine that has a named reference when using callbacks?

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff";
$nysus = $PM . $MCF;
Click here if you love Perl Monks

Replies are listed 'Best First'.
Re: Passing arguments to callback routines with named references
by bobn (Chaplain) on Jul 15, 2003 at 02:13 UTC

    The routine want's a coderef. In the first case you're giving it one. It is a reference to a routine which, when executed, calls change_mode(1).

    In the second case you're giving it the result of calling a subroutine whose reference is held in $change_mode.

    And the third case is the same as the second, the except dereferencing is done with different symbols.

    #!/usr/bin/perl -w use Data::Dumper; $Data::Dumper::Deparse = 1; my $change_mode = \&change_mode; sub change_mode { return "returning: @_ " } #Case 1: Callback routine called directly with anonymous reference Button(-text => 'To Lower Case', -command => sub { &change_mode(1) } ); #Case 2: Callback routine called with argument via named reference ve +r. 1 Button(-text => 'To Upper Case', -command => &{$change_mode}(2) ); #Case 3: Callback routine called with argument via named reference ve +r. 2 Button(-text => 'To Mixed Case', -command => $change_mode->(3) ); sub Button { my %h = @_; print Dumper(\%h); }
    Result:
    $VAR1 = { '-text' => 'To Lower Case', '-command' => sub { &change_mode(1); } }; $VAR1 = { '-text' => 'To Upper Case', '-command' => 'returning: 2 ' }; $VAR1 = { '-text' => 'To Mixed Case', '-command' => 'returning: 3 ' };
    --Bob Niederman, http://bob-n.com
      OK, so in other words, there is no way to use a named reference to a subroutine to supply an argument to a callback routine? You are always forced to use an anonymous subroutine as in case 1?

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff";
      $nysus = $PM . $MCF;
      Click here if you love Perl Monks

        Actually, bobn is not right in this case. Your "case 2" is very close to the mark -- the right way would be:
        my $change_mode = \&change_mode; ... # Case 2: Callback routine called with argument (corrected): # given that $change_mode is a ref to a sub $main->Button(-text => 'To Upper Case', -command => [ $change_mode, 2 ] )->pack;
        The "-command" option on Tk widgets (like the "bind" method) can accept an array ref (anonymous in this case), in which the first element of the array is the subroutine reference, and the remaining elements are args to pass to the sub.

        The "bind" case is a little different, because the sub will actually get the widget handle that invoked it as the first arg every time a "bound" event triggers the sub, whereas the "-command" option does not pass the widget handle as the first arg.

        You can use closures to do it:
        # generate a closure: sub gen_change_mode { my $mode = shift; return { # same code as in change_mode }; } # then do this $main->Button(-text => 'To Upper Case', -command => gen_change_mode(2) )->pack; # or this $some_ref = gen_change_mode(3); $main->Button(-text => 'To Mixed Case', -command => $some_ref )->pack;

        so in the last case gen_change_mode(3) will return a code reference with $mode set as 3, meaning you can pass this around as a code ref, which is what you're after.

        there is an example in `perldoc perlref` which uses a lexical (my) var inside the anon sub that gets returned, so you can pass more arguments to it when you finally run it as a subroutine.

        Yes. Otherwise, it'd be Very Hard to determine automatically whether you meant a subroutine call to execute now or at some point in the future.

        I'm pretty sure that's right. There was another node about that recently.

        --Bob Niederman, http://bob-n.com
Re: Passing arguments to callback routines with named references
by antirice (Priest) on Jul 15, 2003 at 02:13 UTC

    It's pretty simple actually. You cannot store a coderef with arguments. You can create a sub that will call the coderef with the appropriate arguments. Case 2 and Case 3 actually execute the coderef and stores the results. I came across this some time ago with a rather dumb attempt as such:

    $blahref = \&blah(1);

    This would actually execute blah and reference the return value of blah(1). Hope this helps.

    antirice    
    The first rule of Perl club is - use Perl
    The
    ith rule of Perl club is - follow rule i - 1 for i > 1

Re: Passing arguments to callback routines with named references
by sauoq (Abbot) on Jul 15, 2003 at 06:13 UTC

    You've already gotten lots of good explanations and answers. In your case, graff's node sounds like the best answer.

    I just want to offer another suggestion for the general case.

    What is the proper syntax for passing arguments to a subroutine that has a named reference when using callbacks?

    You can use closures, as dank suggested; but you can do it without copying the whole subroutine body and you can make it general.

    sub callback { my $coderef = shift; my @args = @_; sub { $coderef->(@args) } }

    That will allow you to write things like

    -command => callback($change_mode, 3),
    but will work with any coderef (named or not) and arguments.

    -sauoq
    "My two cents aren't worth a dime.";
    
      Or shorter:
        sub callback { shift->( @_ ) }
      
      Liz
        Or shorter:

        No. This is wrong and not just for the obvious reasons. Your "correction",

        sub callback { sub { shift->( @_ ) } }
        is just as wrong.

        The term closure is not synonymous with "code reference." You are returning a reference to a sub that, when called with a coderef and some arguments, immediately calls that coderef with those arguments. And that isn't very useful at all because the reference you return won't be able to be called with arguments anyway!!! So, your shorter "solution" just brings us full-circle to the original problem and fails to add anything but complexity.

        First, have a look at the problem...

        sub callback { my $coderef = shift; my @args = @_; sub { $coderef->(@args) } } sub short_but_wrong { sub { shift->(@_) } } sub foo { print "@_\n" } my $mine = callback( \&foo, 1, 2, 3 ); my $yours = short_but_wrong( \&foo, 1, 2, 3 ); print "Mine: "; $mine->(); print "Yours: "; $yours->();
        That outputs:
        X: 1 2 3 Undefined subroutine &main:: called at cback line 7.
        Why? Because the call, $yours->(), didn't supply an argument so the shift resulted in undef which isn't a valid code reference.

        The whole point of a closure is that it captures its lexical environment. It binds the code with some variables. Your "shortening" eliminates the lexical environment entirely; there is nothing to capture, so it isn't a closure at all.

        Since you need the lexicals, you won't shorten my original (without crunching whitespace and picking shorter identifiers) by much more than this:

        sub callback { my ( $coderef, @args ) = @_; sub { $coderef->( @args ) } }

        -sauoq
        "My two cents aren't worth a dime.";
        
        I don't think that is right.

        You're not building the closure -- you're just calling the coderef.