为什么我不能连接并打印函数返回的值?

Why can't I concatenate and print the value returned by a function?

我有一个调用函数的 Perl 程序,在本例中为 ref,并检查结果。具体来说,我正在测试一个变量是一个散列引用。在这种情况下,ref 将 return 'HASH'。我测试了它,它起作用了。

然后我决定记录它,添加一个 print 来显示同一调用的结果,但它没有正常工作。这是一个简化版本:

use strict;
use warnings;

my $book_ref = {};
$book_ref->{'title'} = 'The Lord of the Rings';

if (ref $book_ref eq 'HASH') {
    print "ref $book_ref is a " . ref $book_ref . "\n";
}

print "Program is over\n";

令我惊讶的是,这是输出:

ref $book_ref is a Program is over

尽管使用了 strictwarnings,但既没有错误也没有警告。
ref 的调用完全相同(它是复制和粘贴),但是虽然它在 if 条件下正常工作,但 print 不显示任何内容,实际上似乎是被打断,因为明显跳过了换行符。为什么行为会改变?

原因是调用函数ref时没有带括号,这导致该行被错误地解析。

  • refif 中被调用时,条件用括号清楚地分隔,这意味着 ref 非常清楚它的参数是什么: $book_ref .没有歧义。
  • 相反,在打印结果时,缺少括号意味着 Perl 将以非预期的方式解析该行:

    1. 首先它将连接 $book_ref"\n"。在标量上下文中,$book_ref 计算为类似于 HASH(0x1cbef70) 的字符串,因此结果是字符串 "HASH(0x1cbef70)\n"
    2. 然后,ref 将在字符串 "HASH(0x1cbef70)\n"producing as output an empty string 上调用:''.
    3. 此时,print打印出空字符串,即nothing,到此为止。换行符 \n 被跳过,因为它已经被 ref 消耗掉了,所以 print 甚至看不到它。而且没有错误。

所有这些都来自 Perl's operator precedence:来自 table、

(...)

 8.    left         + - .
 9.    left         << >>
10.    nonassoc     named unary operators
11.    nonassoc     < > <= >= lt gt le ge
12.    nonassoc     == != <=> eq ne cmp ~~

(...)

其中 "function call" 实际上是一个 "named unary operator" (一元运算符是 ref)。所以第 8 行的 . 运算符比第 10 行的函数调用具有更高的优先级,这就是为什么 print 的结果不是预期的结果。另一方面,函数调用的优先级高于 eq(在第 12 行),这就是为什么在 if 中一切都按预期进行。

优先级问题的解决方案是使用 .

  • 一种可能是用括号强调函数调用:

    print "ref $book_ref is a " . ref($book_ref) . "\n";
    
  • 另一个我不太喜欢但仍然有效的方法是使用括号来隔离必须连接的字符串,方法是将左括号放在 ref:[=43 之前=]

    print "ref $book_ref is a " . (ref $book_ref) . "\n";
    
  • zdim 在评论中建议的另一种可能的方法是使用逗号:

    print "ref $book_ref is a ", ref $book_ref, "\n";
    

当我第一次写 if 时,我决定避免使用括号以使代码更具可读性。然后我复制了它并没有注意到问题。我最终浪费了 2 个小时来查找错误。

最好的解决方案似乎是第一个,因为如果您将它复制到另一个地方(如另一个 print),您保证也复制防止问题的括号。对于第二个,我可能不会意识到括号的重要性,也不会复制它们。而第三个只有当你记住你必须始终使用逗号而不是点时才有效,这是不明显的,因此容易出错。所以,虽然它们有效,但我认为它们不太安全。

其他评论也建议使用 printf,这需要处理格式说明符或表达式插值,例如 print "ref $book_ref is a ${\ ref $book_ref }\n";,我觉得更难阅读。

底线:始终使用括号