Можете ли вы взять ссылку на встроенную функцию в 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"; }
Если это называется просто @list
Perl 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() выполняется во время выполнения, так почему нельзя сделать переопределение во время выполнения непосредственно перед этим?
Единственный способ заставить его работать - сделать ссылку на 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");
Спасибо, Эрик.