Есть ли способ заставить пустой контекст в 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);
}
  1. Он объявляет суб void с помощью get_cvn, (Sub никогда не определяется.) Код в Void.pm экспортирует сабвуфер в вызывающую лексическую область.

  2. Это говорит Perl, что призывает void следовать пользовательскому синтаксису, используя cv_set_call_parser,

  3. Это говорит Perl, что призывает void нужно манипулировать после того, как они скомпилированы с использованием cv_set_call_checker,

  4. Когда Perl встречает вызов void пользовательский парсер извлекает термин, используя parse_termexpr затем изменяет контекст термина на void с помощью op_contextualize,

  5. После этого контролер удаляет вызов 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";
Другие вопросы по тегам