在本系列比较 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
”要少打字得多!
诸如 Array
和 Hash
之类的数据结构也会自动将值放入绑定到结构的容器中。
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 );
如果您想在容器上指定自己的 FETCH
和 STORE
方法,您可以通过绑定到 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。简而言之,变量以及 List、Array 或 Hash 的元素,要么是值(如果是绑定的),要么是容器(如果是赋值的)。每当调用子例程(或方法)时,给定的参数都会被解除容器化并绑定到子例程的参数(除非另有说明)。容器还保留诸如类型约束和默认值之类的信息。将 Nil
赋值给变量会将其返回到其默认值,如果您未指定类型约束,则默认值为 Any
。
1 条评论