The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Sub::Prepend - Prepend code to named subroutines.

SYNOPSIS

use Sub::Prepend 'prepend';

sub foo ($) {
    print "Foo executed with \@_ = (@_).\n";
}

BEGIN {
    prepend foo => sub {
        # This is called before foo executes.
        print "Foo was called with \@_ = (@_).\n";
        push @_, 'and more';
    }
}

my @bar = qw/ foo bar baz /;
foo(@bar); # The prototype is preserved!

__END__
Foo was called with @_ = (3).
Foo executed with @_ = (3 and more).

DESCRIPTION

Sub::Prepend simply conveniently prepends code to named subroutines without any risk of the wrapping itself breaks any existing code. Prepending means that foo and bar below are equivalent (barring caller in the prepended block):

prepend foo => sub {
    ... # prepended
};
sub foo {
    ... # original
}

sub bar {
    {
        ... # prepended
    }
    ... # original
}

This is an initial release, and some things may change for the next version. If you feel something is missing or poorly designed, now is the time to voice your opinion.

A key feature of this modules is what it doesn't do. See below.

Differences with other modules

The goal for a general subroutine wrapper must be to in itself be transparent to the target subroutine. This is currently not possible for wrappers that append code. Thus the act of the wrapping itself, regardless of the added code, may break existing code.

Below is a list of features that Sub::Prepend has, but it also illustrates the problems with the other modules available as CPAN (see "SEE ALSO").

Fully transparent regarding caller.

When the other module try to append code they also must call the subroutine themselves, adding a call frame visible to caller. This will break subroutines that rely on caller. Some modules try to hack around this by overloading caller but that solution will fail for subroutines compiled before the wrapper module was loaded. It can also introduce subtle bugs when other modules try to overload caller and/or use the DB interface.

Sub::Prepend avoids this by using goto &foo which completely hides the intermediate call frame.

Fully transparent regarding return contexts (such as void, scalar, list, lvalue, dereference, count, etc).

Subroutines that utilize Want to add magic for special contexts, such as optimizing for the number of return values in

my ($x, $y) = foo();

a la split, will usually break if wrapped with the other modules. This is because the return value must be saved away to be returned later, and the call done by the wrapper puts the subroutine call in another context. Simple void/scalar/list context is usually handled correctly.

Sub::Prepend avoids this in the same way as the issue above: through goto &foo.

Preserves prototypes.

In order for code like

sub foo ($) { ... }

my @bar;
foo(@bar);

to continue to work as intended for code compiled after the wrapping, the wrapper must set the proper prototype.

EXPORTED FUNCTIONS

Nothing is exported by default. The :ALL tag exports everything that can be exported.

prepend($subname => sub { ... })

Makes any call to the subroutine named by $subname first go through the subroutine referenced by the second argument. If the subroutine name isn't fully qualified the current package is assumed (except for symbols that belong to main, such as ENV and _). The qualify function in the standard module Symbol can be used if you want the name to default to another package:

use Symbol 'qualify';

prepend(
    qualify($name => 'Other::Package') => sub {
        ...
    }
);

Note: The two subroutines share @_:

sub foo { print "@_" }

prepend(foo => sub { unshift @_, 'x' });

foo(1, 2, 3);

__END__
x 1 2 3

No attempt is made to fiddle with the calling context for the prepended code. Instead, you can use caller($Sub::Prepend::CALLER) as a drop-in replacement for caller().

DIAGNOSTICS

Subroutine &%s not defined

(F) You tried to prepend code to a subroutine that wasn't yet defined.

EXAMPLES

Create hybrid singleton classes

Some classes are designed so that class methods act on a shared object, like a singleton class. Let's say foo and bar are attributes, then such a class could look like

package Foo;
use strict;
use Sub::Prepend 'prepend';

sub new { bless {} => shift }

{
    my $singleton;
    sub singleton { $singleton ||= shift()->new }
}

for (qw/ bar baz /) {
    prepend($_ => sub {
        unshift @_, shift()->singleton
            if not ref $_[0];
    });
}

sub bar { $_[0]->{bar} = $_[1] if @_ > 1; return $_[0]->{bar} }
sub baz { $_[0]->{baz} = $_[1] if @_ > 1; return $_[0]->{baz} }

Here I used Sub::Prepend to add the singleton logic to the attributes. Now Foo->bar and Foo->baz act like instance methods working on the default object. This is not necessarily a good idea.

AUTHOR

Johan Lodin <lodin@cpan.org>

COPYRIGHT

Copyright 2007-2008 Johan Lodin. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

Hook::WrapSub

Hook::PrePostCall

Hook::LexWrap