有没有办法在 Perl 中强制 void 上下文?
Posted
技术标签:
【中文标题】有没有办法在 Perl 中强制 void 上下文?【英文标题】:Is there a way to force void context in Perl? 【发布时间】:2014-08-17 13:16:12 【问题描述】:我想知道这只是出于对 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
,在调用context()
之后输出void
?
我理解这很矛盾,因为 void context 可能意味着您并没有真正返回/分配任何东西。但正如我从我所读到的 Perl 工作方式中所了解的那样,这不是什么都没有返回,而是返回值在 void 上下文中执行后被丢弃。
所以,我想知道:有没有一种方法可以强制 void 上下文,就像你可以强制列表或标量上下文一样,当你恰好在调用函数的时刻?
【问题讨论】:
【参考方案1】: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 )
的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);
它使用get_cvn
声明子void
。 (子永远不会被定义。)Void.pm
中的代码会将子导出到调用词法范围。
它告诉 Perl 对 void
的调用遵循使用 cv_set_call_parser
的用户定义语法。
它告诉 Perl 对 void
的调用需要在使用 cv_set_call_checker
编译后进行操作。
当 Perl 遇到对void
的调用时,用户定义的解析器使用parse_termexpr
提取一个术语,然后使用op_contextualize
将术语的上下文更改为void
。
之后,检查器将从操作码树中删除对 void
的调用,同时将其参数(术语)留在后面。
【讨论】:
我对阅读该代码非常感兴趣。当然,除了满足我对了解 Perl 更多古怪角落的渴望之外,我不能要求您将时间花在没有其他目的的事情上。所以,如果你只是因为它可以做到而想做它,我希望你能告诉我。感谢您的回答。 :-) @Francisco Zarabozo, Syntax::Feature::Void 已上传到 CPAN。如果它还没有,它很快就会出现在你最喜欢的镜子上。 (最多一天) 嗯,除了几行之外,它几乎与我之前的 Syntax::Feature::Loop 相同,所以并不难。在我的答案中添加了它如何工作的解释。【参考方案2】:你必须确保函数的返回码绝对没有被使用,例如
context();
1;
当然,return 'void'
没有任何意义作为返回值 (!defined wantarray
),因为不会使用此返回值。
【讨论】:
嗯,这很符合逻辑。但是,问题是:有没有办法强制其他实际上处于无效上下文中的无效上下文? 您可以强制一个函数处于无效上下文中,但当它不是时,您不能让它相信它在无效上下文中。没有多大意义。 您认为该陈述对于任何可能的情况都绝对正确吗? :-) 我认为“在无效上下文中”和“相信在无效上下文中”之间没有区别。它只是一面旗帜。所以我认为这个说法是绝对正确的。但人们可能会惊讶于哪些上下文是无效的,哪些只是乍一看似乎是无效的。【参考方案3】:你实际上在问什么
引用man perldata
:
当您使用
use warnings
pragma 或 Perl 的 -w 命令行选项时,您可能会看到有关在“无效上下文”中无用使用常量或函数的警告。无效上下文仅表示该值已被丢弃,例如仅包含"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 的语法来实现这样的事情,这是可能的,但相当复杂。
ikegami’s answer 中提供了使用默认 Perl 语法可以获得的最佳近似值。
你为什么想要这样的东西?
我认为这个问题是出于纯粹的好奇心,也许是出于更好地理解 Perl 上下文的愿望。在我看来,它没有任何实际用途。正如perldoc -f wantarray
中的示例所暗示的那样,表示 void 上下文的未定义返回值旨在用于加速计算,例如如果没有它可以执行副作用,则避免生成输出。
return unless defined wantarray; # don't bother doing more
my @a = complex_calculation();
return wantarray ? @a : "@a";
【讨论】:
以上是关于有没有办法在 Perl 中强制 void 上下文?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法强制 Java VM 立即执行从 JNI 发送的异常?
有没有办法在 Perl 中读取 MATLAB .mat 文件?