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

时间:2014-08-17 13:16:12

标签: 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
    }
}

输出:

list
scalar

你能想出一种方法来激发在调用say之后输出void的第三个context()吗?

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

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

3 个答案:

答案 0 :(得分:10)

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 )&#39; 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 );如何运作

真正的工作由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);
}
  1. 它使用void声明子get_cvn。 (sub永远不会被定义。)Void.pm中的代码会将sub导出到调用词法范围。

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

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

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

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

答案 1 :(得分:2)

您必须确保绝对不使用该函数的返回码,例如

context();
1;

当然,return 'void'如果没有任何东西需要返回(!defined wantarray)是没有意义的,因为不会使用此返回值。

答案 2 :(得分:1)

你实际要问的是什么

引用man perldata

  

当您使用use warnings pragma或Perl的 -w 命令行选项时,您可能会看到有关在“void context”中无用的常量或函数的警告。 Void上下文仅表示已丢弃该值,例如仅包含"fred";getpwuid(0);的语句。它仍然被视为关注它们是否在列表上下文中被调用的函数的标量上下文。

     

用户定义的子例程可以选择关注它们是在void,标量还是列表上下文中调用。但是,大多数子程序都不需要打扰。那是因为标量和列表都会自动插入到列表中。有关如何动态识别函数的调用上下文,请参阅wantarray

因此,如果在列表或标量上下文中执行调用,则询问是否可以完全抛出函数调用的值。

答案是肯定的!

在标量上下文中,仅使用列表的最后一个元素,在void上下文中计算其他元素。更真实的是:“列表”实际上从来就不是一个列表。在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,因为函数参数总是在列表上下文中计算,除非在prototype中另有说明。原型不能强制虚空化。

你只能通过改变Perl的语法来实现这样的事情,这可能是非常复杂的。

使用默认Perl语法可以获得的最佳近似值显示在ikegami’s answer

你为什么要这样的东西?

我认为这个问题源于纯粹的空闲好奇心,也许是出于更好地理解Perl语境的愿望。在我看来,它没有任何实际用途。作为perldoc -f wantarray中的示例暗示,表示void上下文的未定义返回值旨在用于加速计算,例如,如果没有它就可以进行副作用,以避免产生输出。

return unless defined wantarray; # don't bother doing more
my @a = complex_calculation();
return wantarray ? @a : "@a";