Можете ли вы взять ссылку на встроенную функцию в Perl?

Какой синтаксис, если таковой имеется, может использовать ссылку на встроенный shift?

$shift_ref = $your_magic_syntax_here;

Точно так же, как вы могли бы определить пользовательский sub:

sub test { ... }

$test_ref = \&test;

Я пробовал следующее, которые все не работают:

\&shift
\&CORE::shift
\&{'shift'}
\&{'CORE::shift'}

Ваш ответ может включать XS, если это необходимо, но я бы предпочел нет.

Пояснение: я ищу решение общего назначения, которое может получить полностью функциональную ссылку на код из любого встроенного. Этот код может быть передан в любую функцию более высокого порядка, точно так же, как ссылка на определенную пользователем подпрограмму. Кажется, до сих пор существует консенсус, что это невозможно, кто-то хочет не согласиться?

7 ответов

Решение

Нет, ты не можешь. Какую основную проблему вы пытаетесь решить? Там может быть какой-то способ сделать что угодно.

В ответ на добавленную часть вопроса "Ваш ответ может включать в себя XS, если это необходимо, но я бы предпочел этого не делать". Вызов встроенных функций из XS действительно сложен, так как встроенные модули настроены так, чтобы предполагать, что они выполняются как часть скомпилированного optree. и установить некоторые глобальные переменные. Обычно гораздо проще вызвать некоторую базовую функцию, которую использует встроенная функция, хотя такой функции не всегда бывает, поэтому вы видите такие вещи:

buffer = sv_2mortal(newSVpvf("(caller(%d))[3]", (int) frame));
caller = eval_pv(SvPV_nolen(buffer), 1);

(выполнение строки из XS вместо обхода, необходимого для прямого вызова pp_caller).

Я разобрался с решениями общего назначения для этого и придумал следующий грязный хак с использованием eval. Он в основном использует прототип, чтобы отделить @_, а затем вызвать встроенное. Это было только слегка проверено и использует строковую форму eval, поэтому некоторые могут сказать, что она уже сломана:-)

use 5.10.0;
use strict;
use warnings;

sub builtin {
    my ($sub, $my, $id) = ($_[0], '');
    my $proto = prototype $sub         //
                prototype "CORE::$sub" //
                $_[1]                  //
                ($sub =~ /map|grep/ ? '&@' : '@;_');
    for ($proto =~ /(\\?.)/g) { $id++;
        if (/(?|(\$|&)|.(.))/) {
            $my  .= "my \$_$id = shift;";
            $sub .= " $1\$_$id,";
        } elsif (/([@%])/) {
            $my  .= "my $1_$id = splice \@_, 0, \@_;";
            $sub .= " $1_$id,";
        } elsif (/_/) {
            $my  .= "my \$_$id = \@_ ? shift : \$_;";
            $sub .= " \$_$id,"
        }
    }
    eval "sub ($proto) {$my $sub}"
        or die "prototype ($proto) failed for '$_[0]', ".
               "try passing a prototype string as \$_[1]"
}

my $shift = builtin 'shift';
my @a = 1..10;
say $shift->(\@a);
say "@a";

my $uc = builtin 'uc';
local $_ = 'goodbye';
say $uc->('hello '), &$uc;

my $time = builtin 'time';
say &$time;

my $map = builtin 'map';
my $reverse = builtin 'reverse';
say $map->(sub{"$_, "}, $reverse->(@a));

my %h = (a=>1, b=>2);
my $keys = builtin 'keys';
say $keys->(\%h);

# which prints
# 1
# 2 3 4 5 6 7 8 9 10
# HELLO GOODBYE
# 1256088298
# 10, 9, 8, 7, 6, 5, 4, 3, 2, 
# ab

Пересмотрено ниже и переработано.

Как я понимаю, вы хотите иметь coderef, который будет вызываться для некоторых данных, и это может указывать на некоторую вашу функцию или встроенную функцию.

Если я прав, просто поместите встроенное в закрытие:

#!/usr/bin/perl -w
use strict;

my $coderef = \&test;
$coderef->( "Test %u\n", 1 );

$coderef = sub { printf @_ };
$coderef->( "Test %u\n", 2 );

exit;

sub test {
    print join(' ', map { "[$_]" } @_) . "\n";
}

Делать это с помощью shift также возможно, но помните, что shift без явного массива, с которым можно работать, работает с разными массивами в зависимости от того, где он был вызван.

Вы можете обернуть сдвиг чем-то, на что вы можете ссылаться, но вы должны использовать прототип, чтобы использовать его, поскольку сдвиг особенный.

sub my_shift (\@) { my $ll = shift; return shift @$ll }

Проблема состоит в том, что система-прототип не может волшебным образом выяснить, что когда она вызывает какой-то случайный ref-to-sub в скаляре, ей нужно взять ссылку перед вызовом подпрограммы.

my @list = (1,2,3,4);

sub my_shift (\@) { my $ll = shift; return shift @$ll }

my $a = shift @list;
my $my_shift_ref = \&my_shift;
my $b = (&{$my_shift_ref}  (\@list) ); # see below

print "a=$a, b=$b\n";

for (my $i = 0; $i <= $#list; ++$i) { print "\$list[$i] = ",$list[$i],"\n"; }

Если это называется просто @listPerl Barfs, потому что он не может автоматически ссылаться shift делает.

Смотрите также: [ http://www.perl.com/language/misc/fmproto.html][Tom Christensen's article].

Конечно, для встроенных функций, которые не являются такими особенными, как shift, вы всегда можете сделать

sub my_fork { return fork; }

а потом &my_fork все, что ты хочешь.

Вы можете сделать это, если вы сначала пропатчите внутренний метод (который даст вам код ссылки вашего патча):

use strict;
use warnings;

BEGIN {
    *CORE::GLOBAL::die = sub { warn "patched die: '$_[0]'"; exit 3 };
}

print "ref to patched die: " . \&CORE::GLOBAL::die . "\n";
die "ack, I am slain";

дает вывод:

ref to patched die: CODE(0x1801060)
patched die: 'ack, I am slain' at patch.pl line 5.

Кстати: я был бы признателен, если кто-нибудь может объяснить, почему переопределение должно быть сделано как *CORE::GLOBAL::die скорее, чем *CORE::die, Я не могу найти никаких ссылок для этого. Кроме того, почему переопределение должно выполняться в блоке BEGIN? Вызов die() выполняется во время выполнения, так почему нельзя сделать переопределение во время выполнения непосредственно перед этим?

Если вы хотите увидеть, что нужно, чтобы подделать его в рабочем коде качества, посмотрите на код autodie. Мясо в Fatal. Помогает, если ты безумный пиратский джедай-австралиец.

Единственный способ заставить его работать - сделать ссылку на sub{shift},

perl -e '@a=(1..3); $f=sub{shift}; print($f->(@a), "\n");'

Это функционально эквивалентно:

perl -e '@a=(1..3); print(shift(@a), "\n");'

Который может быть просто perl -e 'print 1, "\n"' но тогда мы не будем говорить о встроенном.

К твоему сведению, я удивлен, что никто не может ссылаться на встроенное, и теперь, когда мне стало ясно, я не могу не думать об этом как о недостатке в Perl.

Обновление Эрика правильно указывает на то, что $f=sub{shift}; $f->(@a) листья @a без изменений. Это должно быть больше похоже на:

perl -e '@a=(1..3); $f=sub{shift @{+shift}}; print($f->(\@a), "\n");

Спасибо, Эрик.

Другие вопросы по тегам