Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying a Subroutine

Tags:

perl

I am trying to apply a prototype to a copy of a subroutine, without modifying the existing subroutine. I.e. this is not ok:

use Scalar::Util 'set_prototype';

sub foo {};
*bar = \&foo;
set_prototype(\&bar, '$');  # also modifes "foo"

What I want to achive can be done with a goto &sub:

sub foo {};
sub bar($) {
    goto &foo;
}

However, this is introduces unnecessary overhead which I am not keen on. Therefore my question: Is there a way to make a (shallow) copy of a subroutine (CV), so that setting the prototype of the copy does not affect the original? I.e. something like

use Scalar::Util 'set_prototype';

sub foo {};
*bar = magical_cv_copy(\&foo);
set_prototype(\&bar, '$');  # does not modify "foo"

I looked at Sub:Clone, but it appears to be out of date and won't install on my system without forcing it. I would prefer not having to write XS code for this.

Test case to clarify my requirements:

use strict;
use warnings;
use Test::More tests => 7;
use Scalar::Util qw/refaddr set_prototype/;

sub foo {
    my ($x) = @_;
    return 40 + $x;
}
*bar = then_a_miracle_occurs(\&foo);

ok not(defined prototype \&foo), 'foo has no prototype';
ok not(defined prototype \&bar), 'bar has no prototype';
isnt refaddr(\&foo), refaddr(\&bar), 'foo and bar are distinct';

set_prototype \&bar, '$';

ok not(defined prototype \&foo), 'foo still has no prototype';
is prototype(\&bar), '$', 'bar has the correct prototype';

is foo(2), 42, 'foo has correct behavior';
is bar(2), 42, 'bar has correct behavior';

sub then_a_miracle_occurs {
    my ($cv) = @_;
    # what goes here?
    # return sub { goto &$cv }
}

In avoidance of the X-Y-Problem:

My X-Problem is that a 3rd-party module defines some function foo without prototypes. Judicious use of prototypes can make this function more elegant to use, so I want to create a copy of that sub, except that it does have a prototype. I cannot make any assumptions about the foo function – it may be also be an XS subroutine.

I cannot directly set the prototype of foo, because I do not wish to interfere with other modules that rely on the original behavior of foo.

So we arrive at my Y-Problem: how to copy a subroutine.

like image 882
amon Avatar asked Mar 04 '14 13:03

amon


2 Answers

The miracle function is probably the internal cv_clone.

You mentioned Sub::Clone, and it seems to do what you want. It comes with a pure-Perl implementation based on the goto trick you described, and an XS implementation that calls cv_clone.

I can't find another module that wraps this internal function. If you have trouble installing the module, I'd suggest you open a RT ticket. There's one older but unresolved ticket already, so you might have to nudge one of the maintainers.

Ideally, this functionality would be part of a module like Sub::Util. We already have Scalar::Util, List::Util, Hash::Util, but nothing for subroutines.

like image 69
nwellnhof Avatar answered Oct 11 '22 18:10

nwellnhof


a 3rd-party module defines some function foo without prototypes. Judicious use of prototypes can make this function more elegant to use, so I want to create copy of that sub, except that it does have a prototype.

All you need is a thin wrapper:

sub foo(&@) { &Real::foo }

or

sub foo(&@) { goto &Real::foo }

The difference is that the latter hides the call to your foo, which makes a difference if Real::foo checks its caller (e.g. builds a stack trace on error).

If your idea of optimization is getting rid of a sub call, you're doing it wrong.

like image 42
ikegami Avatar answered Oct 11 '22 19:10

ikegami