我的Perl愿望清单:不变符号(第一部分)

测验!问题:这一行中我的错误是什么?

is %HASH{answer}, 'forty-two', '%HASH properly filled';

答案:我找到了正确的答案,但是在HASH的符号上出了问题。它应该是

is $HASH{answer}, 'forty-two', '%HASH properly filled';
#  ^ $, not %

不幸的是,在Perl v5.20+上,这两个语句以相同的方式工作!我直到发布这段代码后才发现问题,cpantesters向我展示了我的错误。这是一个简单的修复,但它提醒我,Perl的变体符号可能会让任何级别的程序员出错。如果我能改变Perl 5的一个地方,我会选择不变符号。

当前情况

在Perl中,符号告诉你期待多少个东西。例如,标量$foo是单个值。数组@foo或哈希%foo中的任何单个值(因为它只是一件东西),也使用$,所以$foo@foo%foo都可以指同一变量的不同部分——或者不同的变量。这种“变体符号”的技术是可行的,但它会混淆新的Perl用户,也让我自己出了错。要知道你在数组或哈希中访问的是什么,你必须查看符号和括号。作为提醒

符号 没有括号 [ ](数组访问) { }(哈希访问)
$ $z:标量,即单个值 $z[0]:数组@z的第一个元素 $z{0}:哈希%z在键"0"处的值
@ @z:数组,即值列表 @z[0, 1]:从@z中获取的两个元素列表($z[0], $z[1])(数组切片) @z{0, "foo"}:从哈希%z中获取的两个元素列表($z{0}, $z{foo})
% %z:哈希,即键/值对列表 %z[0, 1]:从数组@z中获取的键和两个值列表(0, $z[0], 1, $z[1])(哈希切片) %z{0, "foo"}:从哈希%z中获取的键和值列表("0", $z{0}, "foo", $z{foo})

使符号成为名称的一部分

为了防止自己重复犯错误,我希望符号成为变量名称的一部分。这不是一个新想法;在Perl、bash和Raku(前身为Perl 6)中,标量就是这样工作的。(见此处)这样,上面的表格看起来会像这样

符号 没有括号 [ ](数组访问) { }(哈希访问)
$ $z:标量,即单个值 $z[0]:不适用 $z{0}:不适用
@ @z:数组,即值列表 @z[0]@z的第一个元素 @z{0}:不适用
% %z:哈希,即键/值对列表 %z[0]:不适用 %z{0}:哈希%z在键0处的值

更简单!对@z的任何引用都会对名为@z的数组进行某种操作。

但是切片怎么办?

例如,@z[0,1]%z{qw(hello there)}等切片从数组或哈希中返回多个值。如果符号@%不再可用于切片,我们需要一个替代方案。Perl家族目前提供了两种模型:后缀解引用(“postderef”)语法和后缀副词。

Perl v5.20+支持postderef,这为我们提供了一个选项。Postderef将名称与切片分开

# Valid Perl v5.20+
$hashref->{a};      # Scalar, element at index "a" of the hash pointed to by $hashref
$hashref->@{a};     # List including the "a" element of the hash pointed to by $hashref
$hashref->%{a};     # List including the key "a" and the "a" element of the hash pointed to by $hashref

切片类型在引用之后,而不是在引用之前的符号之前。对于非引用,这个想法会给我们切片语法,如@array@[1,2,3]%hash%{a}

Raku 提供了另一种选择:“副词”例如 :kv。例如

# Valid Raku
%hash{"a"}          # Single value, element at index "a" of %hash
%hash{"a"}:v;       # The same --- just the value
%hash{"a"}:kv;      # The list including key "a" and the value of the "a" element of %hash

副词(例如,:kv)位于后缀位置,紧接在括号或花括号之后。按照这个模式,切片看起来像 @array[1,2,3]:l%hash{a}:kv。(为了清晰起见,我建议使用 :l,即 list,而不是 Raku 的 :v。Raku 的 :v 可以返回标量或列表。)

所以,我看到的选项是(受后缀引用启发 / 受 Raku 启发)

您想要什么 没有索引 [ ] 访问 { } 访问
标量 $z:标量,即单个值 @z[0]:从数组中获取单个值 %z{0}:在散列 %z 中键 "0" 处的值
值列表 @z:一个数组,即值列表 @z@[0, 1] / @z[0, 1]:l:当前写入的列表 ($z[0], $z[1]) %z@{0, "foo"} / %z{0, "foo"}:l:当前写入的列表 ($z{0}, $z{"foo"})
键/值对列表 %z:一个散列,即键/值对列表 @z%[0, 1] / @z[0, 1]:kv:当前写入的列表 (0, $z[0], 1, $z[1]) %z%{0, "foo"} / %z{0, "foo"}:kv:当前写入的列表 ("0", $z{0}, "foo", $z{"foo"})

您不一定总能得到您想要的

我更喜欢副词语法。它易于阅读,并且借鉴了 Raku 设计中的所有专业知识。然而,我的偏好必须是可以实现的。我不太确信它不需要重大手术。

Perl 解析器根据切片提供的上下文决定如何解释括号内的内容。解析器将 ...@foo[...] 中的 ... 解释为列表(ref)。在 $foo[...] 中,解析器将 ... 视为标量表达式(ref)。对于任何切片语法,Perl 解析器在解析索引表达式时需要知道所需的结果类型。不幸的是,副词形式在解析索引之后才让解析器猜测。

实际上,您可以修改 Perl 解析器以在看到后缀副词之前保存索引。然后解析器可以应用正确的上下文。我为 @arr[expr]:v 编写了一个 概念证明。它不执行任何代码,但它可以解析后缀副词切片而不会崩溃!然而,在编写这段代码时,我遇到了一个惊喜:新语法并没有绑定到 use v5.xx 指令。

实际上,Perl 解析器允许针对任何 Perl 版本编写的代码使用最新语法。以下两个命令行都在 Perl v5.30 上工作

$ perl -Mstrict -Mwarnings -E 'my $z; $z->@* = 10..20'
#                           ^ -E: use all the latest features
$ perl -Mstrict -Mwarnings -e 'my $z; $z->@* = 10..20'   # (!!!)
#                           ^ -e: not the latest features

第二个命令行没有 use v5.30,因此您不能使用(在 v5.10 中引入的)say。但是,您可以使用后缀引用(从 v5.20 开始)!

由于解析器允许旧程序使用新语法,因此任何建议添加到 Perl 语法中的内容在所有以前的 Perl 版本中都必须没有意义。后缀副词未能通过这一测试。例如,以下是一个有效的 Perl 程序

sub kv { "kv" }
my @arr = 10..20;
print 1 ? @arr[1,2]:kv;
        # ^^^^^^^^^^^^ valid Perl 5 syntax, but not a slice :(
print "\n";

我首选的切片语法可能会改变现有程序的含义,所以我看起来无法得到我的首选。

下一步

这并不是故事的结局!在第二部分中,我将更深入地探讨 Perl 的解析器和词法分析器。我将分享我在调查后缀引用时发现的惊喜。然后,我将描述通向不变符号的可能路径以及它们可以提供的简单性。

标签

Christopher White

Chris White是一位经验丰富、多产的发明家、公众演讲者、专利代理人、计算机工程师、演示场景制作人和软件开发者。他目前正在为D3 Engineering构建嵌入式Linux系统。他不定期地在博客上撰写关于技术、音乐和奶酪的文章。

浏览他的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开一个问题或拉取请求来帮助我们。