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

Fellow Monks,

I want to wrap some subroutines with code to inject additional parameters. Hook::LexWrap seems to be the module to do this, but I cannot figure out how.

The pre-wrapper I installed receives the parameters, but is it possible to change those parameters before the wrapped subroutine receives them? The following did not work:

use strict; use Hook::LexWrap; sub some_sub{ print "@_"; } sub before{ # inject a new arg unshift @_, 'new arg'; } wrap some_sub, pre => \&before; some_sub(1,2,3);
An alternative idea is to short-circuit the wrapped subroutine and call it directly from the wrapper, but this obviously just results in endless recursion:
use strict; use Hook::LexWrap; sub some_sub{ print "@_"; } sub before{ $_[-1] = 'short-circuit'; # remove the last arg, which was inserted by Hook::LexWrap pop; # try to call the original sub with new args, # does not work and falls into recursion some_sub ( 'new arg', @_); } wrap some_sub, pre => \&before; some_sub(1,2,3);
Is there a way to get a reference to the wrapped subroutine (ideally including any further nested wrappers)? Or another way to this altogether?

Replies are listed 'Best First'.
Re: Altering subroutine parameters using Hook::LexWrap (ez diy)
by tye (Sage) on Jan 19, 2005 at 03:42 UTC

    Use the source, Luke. The module's code isn't particularly hard to understand. It is clear that you can't change @_ (only elements of @_) in a pre-wrapper and the example with splice in the docs is clearly wrong.

    You can wrap a routine yourself much more flexibly and less magicly with rather simple code:

    sub some_sub { print "(@_)\n"; } my $orig= \&some_sub; *some_sub= sub { $orig->( 'newArg', @_ ); }; some_sub( "test", "ing" );

    produces:

    (newArg test ing)

    Make sense?

    - tye        

Re: Altering subroutine parameters using Hook::LexWrap
by BrowserUk (Patriarch) on Jan 19, 2005 at 04:22 UTC

    Sorry for the false start. I encountered the problem when I played with it last year, and forgot how I got around it.

    I 'fixed' it, by patching 3 lines/ 3 chars in the module:

    use strict; use Hook::LexWrap; =comment With Hook::LexWrap patched as follows: 39: () = $wrapper{pre}->( \@_,$return) if $wrapper{pre}; 49: my $dummy = $wrapper{pre}->( \@_, $return) if $wrapper{ +pre}; 59: $wrapper{pre}->( \@_, $return) if $wrapper{pre}; =cut sub some_sub{ print "Got: @_\n"; print "Context: ", defined wantarray ? wantarray ? 'LIST' : 'SCALAR' : 'VOID', "\n"; } sub before{ # inject a new arg unshift @{ $_[ 0 ] }, 'new arg'; } wrap some_sub, pre => \&before; my @a = some_sub(1,2,3); my $a = some_sub(1,2,3); some_sub(1,2,3); __END__ P:\test>junk4.pl Got: new arg 1 2 3 Context: LIST Got: new arg 1 2 3 Context: SCALAR Got: new arg 1 2 3 Context: VOID

    The caveat is that your wrappers have to expect a reference to @_ and deal with that. Not a major problem, if you need to make modifications.


    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
Re: Altering subroutine parameters using Hook::LexWrap
by Zaxo (Archbishop) on Jan 19, 2005 at 04:09 UTC

    By altering $_[-1] in before(), you've instructed the pre-wrap to short-circuit and not call the wrapped sub at all.

    Access to wrappers is fairly easy, you just need to keep a lexical copy of the wrap call's returned object. You can kill a particular wrapper by undefining that object or letting it go out of scope.

    Changing @_ directly with unshift, shift, push pop or splice in a pre- routine doesn't work. It confuses Hook::LexWrap and doesn't confer aliasing to any added variables. If you want to change an argument, work on individual elements of @_. The only way I've found to change the number of arguments is to design the wrapped sub to take a reference to a data structure and work on that.

    Here's an example from my scratchpad of using Hook::LexWrap,

    #!/usr/bin/perl use strict; use warnings; use Hook::LexWrap; { my $foo; sub foo { @_ ? $foo = shift : $foo; } # _not_ an lvalue! my $wrapper = wrap *foo, pre => sub { warn 0+@_, " @_"; $_[0] = lc $_[0] if @_ > 1; warn 0+@_, " @_"; }, post => sub { $_[-1] = wantarray ? [ map {uc} @{$_[-1]} ] : uc $_[-1] }; sub wrapper () :lvalue { $wrapper } # keeps the cloistered # lexwrap alive sub _foo () :lvalue { $foo } # inspection hatch } my $str = 'Quux'; my $tmp = $str; printf "Given $str, wrapped setter reports %s, backdoor shows %s, arg +is now %s.\n", foo($tmp), _foo, $tmp; # setter printf "Wrapped getter reports %s, and backdoor shows %s\n", foo(), _foo; # getter __END__ 2 Quux ARRAY(0x804b3f8) at hlw.pl line 13. 2 quux ARRAY(0x804b3f8) at hlw.pl line 15. Given Quux, wrapped setter reports QUUX, backdoor shows Quux, arg is n +ow Quux. 1 ARRAY(0x804b50c) at hlw.pl line 13. 1 ARRAY(0x804b50c) at hlw.pl line 15. Wrapped getter reports QUUX, and backdoor shows Quux

    You can keep a clean copy of some_sub() around by wrapping a hard reference to it instead of a name-or-glob. Wrap then returns a reference to a newly constructed sub with wrappers, and the original is unmodified. The pre- wrapper would then be free to call the original if you liked, having short-circuited the wrapping mechanism.

    You may note the "not an lvalue!" comment. Hook::LexWrap does not set the original's attributes for the wrapped version.

    After Compline,
    Zaxo

Re: Altering subroutine parameters using Hook::LexWrap
by Thilosophy (Curate) on Jan 19, 2005 at 04:56 UTC
    Thanks for all the replies. It seems that I cannot do this (easily) with Hook::LexWrap, and the best way is to wrap manually or use alternatives like Hook::WrapSub (at least in cases where one does not need lexical wrappings that unwrap themselves when falling out of scope).

    But this is a bug in Hook::LexWrap, right? The documentation seems to indicate that it should be possible, and there is even example code illustrating this feature (which does not seem to work)

    wrap set_temp, pre => sub { splice @_, 0, 1, $_[0] * 1.8 + 32 }, post => sub { $_[-1] = ($_[0] - 32) / 1.8 };

      It is not simply a bug. The documentation uses splice incorrectly but that example could be fixed by modifying $_[0] instead.

      Fixing this short-coming really requires a change to the documented API. BrowserUk has already implemented that nicely. Now fix the documentation and address backward compatability and you can fix the module.

      - tye        

Re: Altering subroutine parameters using Hook::LexWrap
by merlyn (Sage) on Jan 19, 2005 at 03:23 UTC
    I played with it a bit, and I can't seem to get it to alter @_ either. Even the example from the manpage with splice doesn't seem to actually work... only setting $_[0] explicitly changes anything, and of course that doesn't permit insertion or deletion.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re: Altering subroutine parameters using Hook::LexWrap
by BrowserUk (Patriarch) on Jan 19, 2005 at 03:49 UTC

    Update: As tye points out below, I was wrong.

    You have to return a reference to the modifed @_ from the pre wrapper sub, if you want that to be passed to the original sub.

    use strict; use Hook::LexWrap; sub some_sub{ print "@_"; } sub before{ # inject a new arg unshift @_, 'new arg'; return \@_; } wrap some_sub, pre => \&before; some_sub(1,2,3);

    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.

      This produces "1 2 3" for me using Hook-LexWrap-0.20. (That is, it doesn't work.)

      - tye        

Re: Altering subroutine parameters using Hook::LexWrap
by gaal (Parson) on Jan 19, 2005 at 11:47 UTC
    The Aspect folks ran in to a similar problem and bundle a hacked version of the library together with their code. You may find it useful. Indeed you may find Aspect.pm useful!