@services 被赋值时,右边收到列表上下文;$service 被赋值时,右边收到标量上下文。表达式自己并不携带一个永恒的“返回类型”。perldata 的 Context 一节特别用 <STDIN> 举例:传给 int 与传给 sort 的读入范围可以不同。对日志解析来说,这意味着“读一条”还是“读到 EOF”,不能由变量名字猜出来,必须看调用位置和具体运算符文档。

实验 examples/perl-language/experiments/02-context.pl 定义 describe_context():列表位置给两个服务名,标量位置给 one,丢弃值的位置返回空列表。运行后 STDOUT 前两行是 list=api|worker 和 scalar=one;另有 scalar-array=2、scalar-reverse=ba、list-reverse=b|a,STDERR 空、退出码 0。脚本确实调用了 void 位置,却没把无输出伪装成对其返回值的观测:那里被丢弃的值本就不会打印。wantarray 能区分 void;perldata 同时指出,对只在列表与标量之间选择的函数,void 仍算非列表上下文。两句话不矛盾。

图:函数调用位置向被调用方提供上下文

1
2
3
my @services = describe_context();  # 要求列表 → 两个服务
my $service = describe_context(); # 要求标量 → "one"
describe_context(); # 值被丢弃 → 要看函数是否有副作用

不能据此说“所有表达式在标量位置都是最后一个值”。同一个实验里,scalar(@services) 给元素个数 2,而 scalar(reverse('a','b')) 给拼接后反转的 ba;列表位置的 reverse 却是 b|a。日志工具的 read_records 不靠这些偶然结果表示成功:它约定始终返回数组引用,调用者用 @$records 显式读取集合,需要数量时才写 scalar(@$records)。t/01-04-semantics.t 对五行输出逐字节比较,并记录异常路径;本章不声称它已覆盖所有上下文敏感内置函数。

补充实验 course-semantics.pl variables 给原 01 的 $、@、% 一个并列表:scalar=api、array_count=2 array_last=WARN、hash_INFO=2。这里的 $service 保存一个标量值,@levels 保存有顺序的多个等级,%count 按键保存计数。符号前缀不是装饰,而是读取形状的一部分;把 %count 当数组下标访问,或把 @levels 放进标量位置后还期待最后一个元素,都会把数据结构契约改掉。

同一个测试文件还保留原有上下文断言,所以这一课形成两层结论:变量 sigil 说明容器形状,调用位置说明表达式如何被求值。my $n = @levels 得到数量,my ($first) = @levels 得到首元素;$count{INFO} 是哈希按键读取,不参与数组顺序。官方依据仍是 Perl 5.38.2 perldata 的 Scalar values、Array variables、Hash variables 与 Context。

练习:把 my $total = @services; 写成 my ($first) = @services;,两者会得到同一个值吗?前者将数组放进标量上下文,得到长度 2;后者是列表赋值,取第一个元素 api。下一章用这个区别区分临时列表值和可变数组。

参考:Perl 5.38.2 pod/perldata.pod(Context、List value constructors)、pod/perlfunc.pod(wantarray、reverse);校验方式见 examples/perl-language/VERSIONS.md,执行证据见 examples/perl-language/evidence/01-04.md。