Есть ли способ заставить пустой контекст в Perl?
Мне интересно только из-за любопытства о том, как работает Perl и как далеко вы можете зайти с такими вещами.
Есть некоторые функции, которые написаны так, чтобы действовать по-разному в каждом из трех контекстов.
С помощью следующего кода в качестве очень простого примера:
use 5.012;
say context();
say scalar context();
sub context {
if (wantarray) {
return 'list';
} elsif (defined wantarray) {
return 'scalar';
} else {
return 'void'; # Destined to be discarded
}
}
ВЫХОД:
list
scalar
Можете ли вы придумать способ спровоцировать третий say
что выводит void
после context()
называется?
Я понимаю, что это довольно противоречиво, поскольку пустой контекст может означать, что вы на самом деле ничего не возвращаете / не присваиваете. Но как я понял из прочитанного о том, как работает Perl, речь идет не о том, что ничего не возвращается, а о том, что возвращаемое значение отбрасывается после выполнения в пустом контексте.
Итак, мне интересно: есть ли способ принудительно пустовать контекст таким же образом, как вы можете принудительно использовать список или скалярный контекст, когда вы фактически находитесь в списке или скалярном контексте в момент вызова функции?
3 ответа
sub void(&) { $_[0]->(); () }
say context();
say scalar context();
say void { context() };
Более продвинутый код может дать нам лучший синтаксис:
use syntax qw( void );
say context();
say scalar context();
say void context();
Что касается примечания, следующее показывает, что scalar
это не столько функция, сколько директива времени компиляции:
$ diff -u0 \
<( perl -MO=Concise,-exec -Msyntax=void -E'say f()' 2>&1 ) \
<( perl -MO=Concise,-exec -Msyntax=void -E'say scalar f()' 2>&1 )
--- /dev/fd/63 2014-08-17 12:34:29.124827443 -0700
+++ /dev/fd/62 2014-08-17 12:34:29.128827401 -0700
@@ -7 +7 @@
-6 <1> entersub[t6] lKS/TARG <-- "l" for list context
+6 <1> entersub[t7] sKS/TARG <-- "s" for scalar context
И то же самое касается use syntax qw( void )
"s void
:
$ diff -u0 \
<( perl -MO=Concise,-exec -Msyntax=void -E'say f()' 2>&1 ) \
<( perl -MO=Concise,-exec -Msyntax=void -E'say void f()' 2>&1 )
--- /dev/fd/63 2014-08-17 12:34:41.952692723 -0700
+++ /dev/fd/62 2014-08-17 12:34:41.952692723 -0700
@@ -7 +7 @@
-6 <1> entersub[t6] lKS/TARG <-- "l" for list context
+6 <1> entersub[t6] vKS/TARG <-- "v" for void context
Как use syntax qw( void );
работает
Настоящая работа выполняется синтаксисом:: Feature:: Void 's Void.xs
, чьи ключевые строки следуют:
STATIC OP* parse_void(pTHX_ GV* namegv, SV* psobj, U32* flagsp) {
return op_contextualize(parse_termexpr(0), G_VOID);
}
STATIC OP* ck_void(pTHX_ OP* o, GV* namegv, SV* ckobj) {
return remove_sub_call(o);
}
BOOT: {
const char voidname[] = "Syntax::Feature::Void::void";
CV* const voidcv = get_cvn_flags(voidname, sizeof(voidname)-1, GV_ADD);
cv_set_call_parser(voidcv, parse_void, &PL_sv_undef);
cv_set_call_checker(voidcv, ck_void, &PL_sv_undef);
}
Он объявляет суб
void
с помощьюget_cvn
, (Sub никогда не определяется.) Код вVoid.pm
экспортирует сабвуфер в вызывающую лексическую область.Это говорит Perl, что призывает
void
следовать пользовательскому синтаксису, используяcv_set_call_parser
,Это говорит Perl, что призывает
void
нужно манипулировать после того, как они скомпилированы с использованиемcv_set_call_checker
,Когда Perl встречает вызов
void
пользовательский парсер извлекает термин, используяparse_termexpr
затем изменяет контекст термина наvoid
с помощьюop_contextualize
,После этого контролер удаляет вызов
void
из дерева кода операции, оставляя его аргумент (термин) позади.
Вы должны убедиться, что код возврата функции определенно не используется, например,
context();
1;
И, конечно, нет смысла return 'void'
если ничего не требуется в качестве возврата (!defined wantarray
), потому что это возвращаемое значение не будет использоваться.
Что вы на самом деле спрашиваете
квотирование man perldata
:
Когда вы используете
use warnings
При использовании параметра командной строки Perl -w вы можете увидеть предупреждения о бесполезном использовании констант или функций в "пустом контексте". Пустой контекст просто означает, что значение было отброшено, например, оператор, содержащий только"fred";
или жеgetpwuid(0);
, Он по-прежнему считается скалярным контекстом для функций, которые заботятся о том, вызываются они или нет в контексте списка.Пользовательские подпрограммы могут позаботиться о том, вызываются ли они в пустом, скалярном или списочном контексте. Однако большинство подпрограмм не нужно беспокоить. Это потому, что и скаляры и списки автоматически интерполируются в списки. См. Wantarray, чтобы узнать, как динамически распознавать контекст вызова вашей функции.
Таким образом, вы спрашиваете, возможно ли полностью отбросить значение вызова функции, если вызов выполняется в списке или скалярном контексте в противном случае.
Ответ - да!
В скалярном контексте используется только последний элемент списка, остальные элементы оцениваются в пустом контексте. Еще более верно: "список" фактически никогда не был списком. Читайте больше о поведении оператора запятой в скалярном контексте в man perlop для объяснения. Это также объясняется другими словами в конце раздела описания в man perlfunc. И наконец perldoc -f scalar
упоминает об этом тоже.
В контексте списка такого прямого пути не существует. Вам нужно использовать тот же трюк, что и выше, чтобы получить произвольный скаляр (предпочтительно 0), а затем избавиться от него, чтобы он не влиял на содержимое вашего списка. Пустой список повторений - это то, что вы ищете. (Кстати, оператор повторения оценивает свой второй операнд в скалярном контексте.)
sub test_context() {
wantarray and die "list\n";
defined wantarray and die "scalar\n";
die "void\n";
}
$\ = "\n"; # to make output prettier
### Uncomment the one you want to test.
# -- Somewhat canonical examples of contexts
#[test_context]; # list (+ warning of class 'void')
#print test_context; # list
#scalar(test_context); # scalar (forces scalar context anywhere)
#my $x = test_context; # scalar
#test_context; # void
#
# -- Examples of forcing void context
# Replace test_context with a fixed scalar and try again to see that even if
# the function returned a value, it would get discarded. Ignore the 'void' warning.
#print my $x = (test_context, 42);
#print '^', () x (test_context, 0), '$';
Предостережение: нет void()
Вы не можете создать void
функция, использование которой будет похоже на один из scalar
,
sub void {
();
}
print void(test_context);
Это приведет к test
вызывается в контексте списка, потому что параметры функции всегда оцениваются в контексте списка, если в прототипе не указано иное. И прототипы не могут заставить пустой контекст.
Вы можете реализовать такую вещь только путем изменения синтаксиса Perl, что возможно, но довольно сложно.
Наилучшее приближение, которое вы можете получить с помощью синтаксиса Perl по умолчанию, представлено в ответе ikegami.
Зачем тебе такая вещь?
Я предполагаю, что этот вопрос возник из чистого любопытства и, возможно, из желания лучше понять контексты Perl. На мой взгляд, он не имеет никакого реального смысла. Как пример в perldoc -f wantarray
подразумевается, что неопределенное возвращаемое значение, представляющее пустой контекст, предназначено для использования для ускорения вычислений, например, чтобы избежать генерации вывода, если побочные эффекты могут быть выполнены без него.
return unless defined wantarray; # don't bother doing more
my @a = complex_calculation();
return wantarray ? @a : "@a";