Perl哈希表嵌套的原理

4

我决定尝试使用Perl,但我遇到了一种似乎是有效的语言结构,但我无法相信它是真实存在的。因为我猜想这背后有一些理由,所以我决定提出一个问题。

以下是Perl代码:

%data = ('John Paul' => ('Age' => 45), 'Lisa' => 30);
print "\$data{'John Paul'} = $data{'John Paul'}{'Age'}\n";
print "\$data{'Lisa'} = $data{'Lisa'}\n";

我的意图是检查哈希嵌套的工作原理。上面的代码输出:
$data{'John Paul'} =
$data{'Lisa'} =

要使它成为有效的哈希散列,需要:

%data = ('John Paul' => {'Age' => 45}, 'Lisa' => 30);

结果将会是:

$data{'John Paul'} = 45
$data{'Lisa'} = 30

有人知道吗:

  1. 为什么内部哈希表需要使用{}而不是(),导致非统一性问题?
  2. 当内部哈希表使用()而不是{}时,为什么我没有收到任何错误或警告?这种错误很容易犯。此外,('Age' => 45) 不仅会破坏 'John Paul' 的值,也会破坏 'Lisa' 的值。我无法想象在包含数千行代码的项目中寻找这类“错误”。

括号在Perl中仅表示列表分组和优先级,它们不会创建任何东西。例如(1, 2, 3)(1, (2, 3)(((1), ((2, (3)))))是相同的。它们唯一真正的功能是开始一个列表。 - lordadmira
为了验证您放入数据结构中的内容,请使用Data::Dump(以及许多其他选项)将其转储出来。 use Data::Dump“pp”; pp%data; - lordadmira
2个回答

4
( 'John Paul' => ( 'Age' => 45 ), 'Lisa' => 30 )

是另一种书写方式

'John Paul', 'Age', 45, 'Lisa', 30

括号本身不会创建任何数据结构,它们只会影响优先级,比如像 (3+4)*5。我们不编写以下代码的原因是:
my %h = a => 4;

或其等效物

my %h = 'a', 4;

这将被解释为

( my %h = 'a' ), 4;

创建哈希的是`my %data`,而不是括号。赋值语句的右侧只是将任意数量的标量放在堆栈上,而不是哈希表。赋值运算符将这些标量添加到哈希表中。
但有时,我们希望创建匿名哈希表,这就是`{}`的作用。
my %data = ( 'John Paul' => { 'Age' => 45 }, 'Lisa' => 30 );

基本上等同于

my %anon = ( 'Age' => 45 );
my %data = ( 'John Paul' => \%anon, 'Lisa' => 30 );

请注意,\%anon 返回一个标量,即哈希的引用。这与 ( 'John Paul' => \%anon, 'Lisa' => 30 )'John Paul' => \%anon, 'Lisa' => 30 返回的四个标量有根本的不同。


为什么内部哈希需要使用{}而不是()

这个问题的一个基本前提是错误的:哈希表并不需要使用()。例如,以下用法是完全合法的:

my %h1 = 'm'..'p';

sub f { return x => 4, y => 5 }
my %h2 = f();

my %h3 = do { i => 6, j => 7 };

()和哈希无关。不均匀性来自于缺乏并行性。使用{}来创建哈希表,使用()来覆盖优先级。

由于括号只影响优先级,因此可以使用

my %data = ( 'John Paul' => ({ 'Age' => 45 }), 'Lisa' => 30 );  # ok (but weird)

这与下面的内容非常不同:
my %data = ( 'John Paul' => ( 'Age' => 45 ), 'Lisa' => 30 );  # XXX

为什么使用括号()而不是{}表示内部哈希时没有出现错误或警告?
不仅使用括号()有效,还经常需要在包含逗号的表达式周围使用括号()。那么它应该在什么情况下发出警告呢?问题在于这是否应该成为警告或perlcritic发现的内容至少乍一看是有争议的。后者肯定会发现这个问题,但我不知道是否存在一个规则来解决它。

细微的问题。my %data 并没有直接创建哈希,它告诉 Perl 在作用域和存储方面创建什么类型的哈希。有各种定义哈希的方法,它们都会创建一个哈希表,并且可以创建一个匿名哈希表,该哈希表永远不与哈希变量关联。只有引用哈希表时才会创建哈希表。 - lordadmira
@lordadmira,“* my%data本身不会创建哈希表 *”这句话是错误的。my有编译时和运行时的效果。my%hash的编译时效果是创建一个哈希表,而它的运行时效果是在作用域退出时清除或替换哈希表为一个新哈希表。您可以通过{}创建匿名哈希表,也可以通过简单地提及其名称来创建全局哈希表,但这并不改变其本质。 - ikegami
为什么会在编译时创建一个变量,然后在进入作用域时将其丢弃并创建一个新变量? my() 的目的是使Perl在遇到语句时自动生成变量,并捕获所有引用该符号的引用作为对该面板的引用。 编译时唯一的影响是将符号合法化为strict - lordadmira
@lordadmira,关于“为什么会在编译时创建一个变量,然后在进入作用域时将其丢弃并创建一个新的变量??”,它不会这样做。我说过,当作用域退出时,它可以创建一个新的哈希表。/// 关于“my()的目的是让Perl自动vivify”,您似乎没有理解“auto”的含义。如果您明确使用指令(my)来执行此操作,则不是自动的。当您对未定义的变量进行解引用时,自动vivification是自动创建变量和对它们的引用。 - ikegami

2
为什么不统一,内部哈希表需要使用 {} 而非 ()?
哈希表的赋值是标量列表(键和值交替)。
你不能将哈希表(因为它不是标量)作为值,但可以使用哈希引用。
列表会被展开。
为什么当内部哈希表使用 () 而非 {} 时,我没有收到任何错误或警告消息?
因为你没有打开 use strict; use warnings; pragma(出于可怕的向后兼容性原因,默认情况下关闭,但在 Perl 7 中将默认开启)。

那么对于打破 'Lisa' 的值呢? - Al Bundy
{a=>b} 表示一个标量值,(a=>b) 表示两个标量值... 请参阅 https://perldoc.perl.org/perlreftut。 - clamp
@AlBundy - Lisa没有值,列表被压平了。Lisa是键45的值。 - Quentin
既不严格也不警告捕获此错误(尽管它们会捕获特定示例中的奇数元素)。可能存在一个perlcritic规则。 (如果不存在,可以创建一个。) - ikegami
如果你是Perl的新手,最好加上use diagnostics;。这样你会得到一个非常好的解释,告诉你发生了什么错误。 - lordadmira

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接