日志服务名 ('api', 'worker') 是一次表达式产生的列表值;@services 是保存元素、可供后续 push 的数组变量。二者看起来都写成一串值,但在标量位置并不相同。Perl 5.38.2 perldata 的 List value constructors 明确区分:标量位置的列表字面值取最后一个元素;数组在标量位置给长度。拿前一种规则推断后一种,会把“两个服务”误看成服务 worker。

examples/perl-language/experiments/03-arrays.pl 从两个名字创建数组、复制到 @copy,只给副本追加 cache,再从原数组取切片。执行命令 perl examples/perl-language/experiments/03-arrays.pl:STDOUT 是 source=api|worker、copy=api|worker|cache、slice=api|worker、length=2 四行;STDERR 空、退出码 0。@copy = @services 在这里复制了字符串元素,所以追加副本不修改原数组。这个观察不能推广为“复制任何数组都深拷贝”:数组元素以后若是引用,复制的是引用值,指向的对象仍共享。

日志工具因此有两层边界:read_records 返回集合的引用,避免调用位置偷换返回形状;每一条记录是独立哈希引用,字段次序只在 format_record 显式指定。report 计算计数时用 scalar(@$records) 表示条数,输出时 sort keys %counts 固定顺序;未排序的哈希遍历不能当作稳定金标。更复杂的引用别名要到 05 才能验收,这里只用字符串副本作对照。

补充断言把“列表值”和“数组容器”与哈希一起固定:course-semantics.pl variables 的 array_count=2 array_last=WARN 只说明 @levels 当前保存两个元素,hash_INFO=2 说明 %count 的 INFO 键映射到计数 2。列表字面值 ('INFO','WARN') 不能被 push;数组变量可以被 push;哈希变量没有“最后一个元素”这种契约。课程测试用三种形状的输出防止后续讲解把列表、数组和哈希混作一种“集合”。

course-semantics.pl list_context 继续把表达式上下文写成可复跑输出:list_reverse=b|a scalar_reverse=ba 来自同一个 reverse 在列表位置和标量位置的不同规则;context_seen=list|scalar|void 来自一个探针子例程在三种调用位置下读取 wantarray。void 位置不是“返回空列表”,而是调用者丢弃返回值;如果函数有副作用,副作用仍发生。旧 02-context.pl 已有 scalar-reverse=ba 与 list-reverse=b|a,本次把 void 位置也纳入累计 TAP,避免只讲 list/scalar 两格。

[PATTERN] 先问值有没有名字,再问名字指向哪种容器。临时列表值只在表达式求值期间存在;数组和哈希是可以被后续语句修改的变量;引用则把容器身份作为标量值传递到别处。这个顺序能解释 05 的浅复制:复制数组元素时,若元素本身是引用,复制到的是同一个目标的地址值,而不是目标对象的深副本。

练习:把 push @copy, 'cache' 改为 push @services, 'cache',length 应变多少?应变为 3;如果切片仍明确写 [0, 1],结果仍只包括前两项。可复核命令、期望和运行输出在 examples/perl-language/evidence/01-04.md,并由 t/01-04-semantics.t 断言。

参考:经 SHA-256 校验的 Perl 5.38.2 上游发行包 pod/perldata.pod(List value constructors、Subscripts),pod/perlref.pod(Making References);文档身份见 examples/perl-language/VERSIONS.md。