有没有办法在Perl中强制使用void上下文?

Fra*_*ozo 4 perl perl-context

我想知道这只是出于对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
    }
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT:

list
scalar
Run Code Online (Sandbox Code Playgroud)

你能想办法挑起第三say是输出void后,context()被称为?

我理解这是相当矛盾的,因为无效的上下文可能意味着你并没有真正地返回/分配任何东西.但正如我从Perl工作方式上所读到的那样理解,它并不是什么都没有返回,而是返回值在void上下文中执行后被丢弃.

所以,我想知道:有没有办法以强制列表或标量上下文的方式强制 void上下文,当你碰巧在调用函数时实际上在列表或标量上下文时?

ike*_*ami 10

sub void(&) { $_[0]->(); () }

say        context();
say scalar context();
say void { context() };
Run Code Online (Sandbox Code Playgroud)

更高级的代码可以为我们提供更好的语法

use syntax qw( void );

say        context();
say scalar context();
say void   context();
Run Code Online (Sandbox Code Playgroud)

在旁注中,以下显示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
Run Code Online (Sandbox Code Playgroud)

而且同样适用于use syntax qw( void )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
Run Code Online (Sandbox Code Playgroud)

怎么use syntax qw( void );工作

真正的工作是由Syntax :: Feature :: Void完成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);
}
Run Code Online (Sandbox Code Playgroud)
  1. 它声明了子void使用get_cvn.(sub永远不会被定义.)Void.pmwill中的代码将sub导出到调用词法范围.

  2. 它告诉Perl调用void遵循用户定义的语法cv_set_call_parser.

  3. 它告诉Perl void在使用它们编译后需要对它进行操作cv_set_call_checker.

  4. 当Perl遇到调用时void,用户定义的解析器使用术语提取术语parse_termexpr,然后将术语的上下文更改为void使用op_contextualize.

  5. 然后,检查器void从操作码树中删除调用,同时将其参数(术语)留在后面.