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

Looking for some ideas / advice...

I have a method that offers a callback feature. The running context has a variable $meta declared lexically using my. I would like to make that variable available to the callback function somehow. I have considered the following options:

  • Don't use my; use our, and then local to set the running context. This solution requires the calling context to know about the variable, which means it fails "use strict" (see example code below). Declaring the variable in the calling context would pass "use strict", but due to the way closures work, the local value would not be available in the running context, so this doesn't work.
  • Pass the variable as an argument to the callback. This solution is inconvenient because it so happens that the number of arguments to the callback vary. HTML::Parser has a solution to this where the caller sets the arguments it wants to be passed.
  • Make the variable available to the callback as a package global. This is the solution used by File::Find for example, which requires the variable to be global (though it can be set local in the running context), yields long variable names ($File::Find::dir, etc), and gets more complex when inheritance is involved.
  • Use a "special" variable. This solution works nicely. I'm not aware of any way to declare a variable $meta as being "special" however, so a different name must be used, such as: local ${^_meta} = 1;
  • I also tried eval (see below), but this didn't work (I didn't really expect it to, but worth a shot).
  • All these solutions seem ugly to me, but just wondering if anyone has other ideas or sees any reason to prefer one method over the other...

    #!/usr/bin/perl #use strict; package pkg1; sub test1 { my $meta = 1; $_[0]->(1); eval { $_[0]->(2) }; eval '$_[0]->(3)'; } package pkg2; { pkg1::test1 ( sub { print "$_[0]: meta=$meta;\n"; } ); }

    Replies are listed 'Best First'.
    Re: Accessing variable in callback running context
    by moritz (Cardinal) on Oct 13, 2011 at 05:17 UTC

      Pass a hash or hash reference to the callback; that way it's easy for the callback writer to focus on just those parameters that he wants.

      Or depending on context you work in, it might make sense to encapsulate most of the information in an object that you pass to the callback.

      Or you can simply pass it as a normal argument, and have the callback writer check scalar @_ to find how many are passed (though that's less convenient than a hash(ref)).

      It might (or might not) be also an XY Problem -- if you describe the broader context of your problem, maybe we can find a better solution altogether.

    Re: Accessing variable in callback running context
    by ikegami (Patriarch) on Oct 13, 2011 at 05:23 UTC
      ${ caller()."::meta" } = 1;

      Caveat: Assumes the calling package and the package in which the sub was compiled is the same, so the following won't work:

      package pkg3; sub foo { print "$_[0]: meta=$meta;\n"; }; package pkg2; pkg1::test1(\&pkg3::foo);

      $_, $a and $b are usually used since strict doesn't complain about them. $_ is truly global, but $a and $b require using caller as above.

        I've done this with $a and $b before, but isn't it a bit risky to set a variable with any other name in some random namespace?
    Re: Accessing variable in callback running context
    by brianski (Novice) on Sep 09, 2013 at 22:27 UTC

      I've played around with this a few times. Most recently, I wound up using shared variables with our() and string eval'ing the sub. I had this luxury because my callback is usually called a few thousand times at a go, so the string eval doesn't hurt much. This way, I could use as many variables as I want, tied behind the scenes to the functions that did the real work. The ugliness from the user's point of view is that you have set_callback( q{...}, $opts ) instead of set_callback( sub{...}, $opts) or so, but that's necessary unless we want to use fully qualified variables.

      In the end, it made my code uglier and slower than I wanted it to be (30 lines of e.g. $var1 = $hr->{var1} gets old), so I wound up scaling it back and just using 2 shared variables, $t and $d. Typing $t->{foo} just isn't that much more work than typing $foo, and it keeps the inner code from being needlessly horrific.

      I'm sure there are better solutions, but this is one. HTH.