Copy routine

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

use Scalar::Util 'set_prototype';

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

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

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

However, this introduces unnecessary overhead for which I am not interested. So my question is: Is there a way to make a (shallow) copy of a subroutine (CV) so that installing a copy prototype does not affect the original? Ie something like

use Scalar::Util 'set_prototype';

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

I looked Sub:Clone, but it seems to be outdated and will not install it on my system without forcing it. I would rather not write XS code for this.

Test version 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 }
}

To avoid the XY problem:

X- , foo . , , , . foo - XS.

foo, , foo.

, Y-: .

+4
5

, , cv_clone.

Sub::Clone, , , , . pure-Perl, goto, , XS, cv_clone.

, . , RT. , , .

, Sub::Util. Scalar::Util, List::Util, Hash::Util, .

+6

foo . , , , .

, , :

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

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

, foo, , Real:: foo (, ).

, .

+2

Perl , B:: Deparse, eval . sub , PadWalker.

, , :

#!/usr/bin/env perl

use strict;
use warnings;

{
   package Sub::Clone2;
   use PadWalker;
   use B::Deparse;
   use Sub::Identify;

   sub clone_sub
   {
      my ($orig) = @_;
      my $closed_over = PadWalker::closed_over($orig);

      my $orig_pkg = Sub::Identify::stash_name($orig);
      my $orig_code = B::Deparse->new->coderef2text($orig);
      my $decl = join(q[,], sort keys %$closed_over);
      my $clone = eval sprintf('package %s; my(%s); sub %s', $orig_pkg, $decl, $orig_code)
         or die($@);

      PadWalker::set_closed_over($clone, $closed_over);
      return $clone;
   }
}

{
   package Local::Test;

   my $var = 40; # variable to close over

   sub foo {
      my $total = 0;
      $total += ++$var;
      $total += $_ for @_;
      return $total;
   }

   sub reset {
      $var = 40;
   }
}

my $orig   = \&Local::Test::foo;

print "TESTING THE ORIGINAL FUNCTION\n";
print "$_\n"
   for $orig->(1), $orig->(2, 3), $orig->(4, 5, 6);

Local::Test::reset();

my $cloned = Sub::Clone2::clone_sub($orig);

print "TESTING THE CLONED FUNCTION\n";
print "$_\n"
   for $cloned->(1), $cloned->(2, 3), $cloned->(4, 5, 6);

Local::Test::reset();

, our - , , . ( , , .)

clone_sub , , , , goto, .

+1

, :

, , , foo ?

, , , foo. , " " , , :

*bar = set_prototype(sub { return foo(@_); }, '$');
0

[ , OP , , . , - , .]

, .

, -, , , sub, .

use strict;
use warnings;

use Scalar::Util qw( set_prototype );

sub x {
   my $x;
   return sub { $x if 0; print("$_[0]\n"); };
}

my @a = qw( d e f );

BEGIN {
   my $f = x();
   set_prototype(\&$f, '@');
   *f = $f;
}

f(@a);   # d

BEGIN {
   my $g = x();
   set_prototype(\&$g, '$');
   *g = $g;
}

g(@a);   # 3
f(@a);   # d
0

Source: https://habr.com/ru/post/1530016/


All Articles