Perl 6 中的容器

在本系列比较 Perl 5 和 Perl 6 的第三篇文章中,了解如何在 Perl 6 中处理引用、绑定和容器。
237 位读者喜欢这篇文章。
Automated provisioning in Kubernetes

Opensource.com

在本系列比较 Perl 5 和 Perl 6 的第一篇文章中,我们研究了将代码迁移到 Perl 6 时可能遇到的一些问题。在第二篇文章中,我们研究了 Perl 6 中的垃圾回收工作原理。在这里,在第三篇文章中,我们将重点关注 Perl 5 的引用以及它们在 Perl 6 中的处理方式,并介绍绑定和容器的概念。

引用

Perl 6 中没有引用,这对于许多习惯了 Perl 5 语义的人来说是令人惊讶的。但请不要担心:因为没有引用,您不必担心是否应该取消引用。

# Perl 5
my $foo = \@bar;   # must add reference \ to make $foo a reference to @bar
say @bar[1];       # no dereference needed
say $foo->[1];     # must add dereference ->

# Perl 6
my $foo = @bar;    # $foo now contains @bar
say @bar[1];       # no dereference needed, note: sigil does not change
say $foo[1];       # no dereference needed either

有人可能会认为 Perl 6 中的一切都是引用。从 Perl 5(对象是祝福的引用)的角度来看,这对于 Perl 6 来说是一个合乎逻辑的结论,因为一切都是对象(或可以被视为对象)。但这并不能公正地反映 Perl 6 的情况,并且会阻碍您理解 Perl 6 中的工作方式。谨防虚假朋友

绑定

在我们讨论赋值之前,重要的是要理解 Perl 6 中的绑定概念。您可以使用 := 运算符将某些内容显式绑定到其他内容。当您定义词法变量时,可以将值绑定到它

my $foo := 42;  # note: := instead of =

简而言之,这会在词法 pad (lexpad)(您可以将其视为编译时哈希,其中包含有关在该词法作用域中可见的事物的信息)中创建一个名为“$foo”的键,并将 42 作为其字面值。因为这是一个字面常量,所以您无法更改它。尝试这样做会导致异常。所以不要那样做!

此绑定操作在许多情况下在后台使用,例如在迭代时

my @a = 0..9;    # can also be written as ^10
say @a;          # [0 1 2 3 4 5 6 7 8 9]
for @a { $_++ }  # $_ is bound to each array element and incremented
say @a;          # [1 2 3 4 5 6 7 8 9 10]

如果您尝试迭代常量列表,则 $_ 将绑定到字面,您不能递增这些值

for 0..9 { $_++ }  # error: requires mutable arguments

赋值

如果您比较 Perl 5 和 Perl 6 中“创建词法变量并赋值给它”,则从表面上看它们是相同的

my $bar = 56;  # both Perl 5 and Perl 6

在 Perl 6 中,这会在 lexpad 中创建一个名为“$bar”的键。但是,不是直接将值绑定到该 lexpad 条目,而是为您创建一个容器(一个 Scalar 对象),并将该容器绑定到“$bar”的 lexpad 条目。然后,56 作为值存储在该容器中。在伪代码中,您可以将其视为

my $bar := Scalar.new( value => 56 );

请注意,Scalar 对象是绑定的,而不是赋值的。在 Perl 5 中,最接近这种情况的是tied scalar。但当然,“= 56”要少打字得多!

诸如 ArrayHash 之类的数据结构也会自动将值放入绑定到结构的容器中。

my @a;       # empty Array
@a[5] = 42;  # bind a Scalar container to 6th element and put 42 in it

容器

Scalar 容器对象对于 Perl 6 中的大多数操作是不可见的,因此大多数时候您不必考虑它。例如,每当您使用变量作为参数调用子例程(或方法)时,它都会绑定到容器的值。并且因为您无法赋值给值,所以您会得到

sub frobnicate($this) {
    $this = 42;
}
my $foo = 666;
frobnicate($foo); # Cannot assign to a readonly variable or a value

如果您想允许赋值给外部值,您可以将 is rw 特征添加到签名中的变量。这将签名中的变量绑定到指定变量的容器,从而允许赋值

sub oknicate($this is rw) {
    $this = 42;
}
my $foo = 666;
oknicate($foo); # no problem
say $foo;       # 42

代理

从概念上讲,Perl 6 中的 Scalar 对象具有 FETCH 方法(用于生成对象中的值)和 STORE 方法(用于更改对象中的值),就像 Perl 5 中的 tied scalar 一样。

假设您稍后将值 768 赋值给 $bar 变量

$bar = 768;

发生的事情在概念上等同于

$bar.STORE(768);

假设您想将 20 添加到 $bar 中的值

$bar = $bar + 20;

概念上发生的事情是

$bar.STORE( $bar.FETCH + 20 );

如果您想在容器上指定自己的 FETCHSTORE 方法,您可以通过绑定Proxy 对象来做到这一点。例如,要创建一个始终报告赋值给它的值两倍的变量

my $double := do {  # $double now a Proxy, rather than a Scalar container
    my $value;
    Proxy.new(
      FETCH => method ()     { $value + $value },
      STORE => method ($new) { $value = $new }
    )
}

请注意,您将需要一个额外的变量来保存存储在此类容器中的值。

约束和默认值

除了值之外,Scalar 还包含额外的信息,例如类型约束和默认值。采用以下定义

my Int $baz is default(42) = 666;

它创建一个名为“$baz”的 Scalar 绑定到 lexpad,将该容器中的值约束为成功智能匹配 Int 的类型,将容器的默认值设置为 42,并将值 666 放入容器中。

将字符串赋值给该变量将因类型约束而失败

$baz = "foo";
# Type check failed in assignment to $baz; expected Int but got Str ("foo")

如果您在定义变量时未给出类型约束,则将假定 Any 类型。如果您未指定默认值,则将假定类型约束。

Nil(Perl 6 中等同于 Perl 5 的 undef)赋值给该变量将将其重置为默认值

say $baz;   # 666
$baz = Nil;
say $baz;   # 42

总结

Perl 5 具有值和对值的引用。Perl 6 没有引用,但它有值和容器。Perl 6 中有两种类型的容器:Proxy(很像 Perl 5 中的 tied scalar)和 Scalar。简而言之,变量以及 ListArrayHash 的元素,要么是值(如果是绑定的),要么是容器(如果是赋值的)。每当调用子例程(或方法)时,给定的参数都会被解除容器化并绑定到子例程的参数(除非另有说明)。容器还保留诸如类型约束和默认值之类的信息。将 Nil 赋值给变量会将其返回到其默认值,如果您未指定类型约束,则默认值为 Any

Elizabeth Mattijsen
Elizabeth Mattijsen 自 1978 年以来一直以编程为生,使用过各种(现在大多已废弃的)编程语言,然后才开始使用 Perl 4 编程。1994 年,她创立了荷兰第一家商业网站开发公司,使用 Perl 5 作为主要编程语言。从 2003 年起,她参与了一家在线酒店预订服务的快速增长。

1 条评论

唉,一个错误已经悄悄潜入:在第一个 Perl 5 示例中,它应该说

say $bar[1]; # 无需解引用

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.