列表自计算机出现之前就一直是计算的核心部分,在此期间,许多恶魔在它们的细节中安家落户。它们实际上是 Raku 设计中最难的部分之一,但通过坚持和耐心,Raku 终于拥有了一个优雅的系统来处理它们。

字面列表§

字面 List 使用逗号和分号创建,**不**使用括号,因此

12;                # This is two-element list 
our $list = (12);  # This is also a List, in parentheses 
$list = (12);      # same List (see below) 
$list = (1);         # This is not a List, just a 1 in parentheses 
$list = (1,);        # This is a one-element List

有一个例外,空列表只用一对括号创建。

();          # This is an empty List 
(,);         # This is a syntax error 

请注意,只要列表的开头和结尾清晰,悬挂逗号是可以的,因此请随意使用它们来方便代码编辑。

括号可以用来标记List的开头和结尾,所以

(12), (12); # This is a list of two lists.

ListList也可以通过组合逗号和分号来创建。这也称为多维语法,因为它最常用于索引多维数组。

say so (1,23,4eqv ((1,2), (3,4));
# OUTPUT: «True␤» 
say so (1,23,4;) eqv ((1,2), (3,4));
# OUTPUT: «True␤» 
say so ("foo";) eqv ("foo"eqv (("foo")); # not a list 
# OUTPUT: «True␤»

与逗号不同,悬挂分号不会在字面量中创建多维列表。但是,请注意,这种行为在大多数参数列表中会发生变化,其中确切的行为取决于函数... 但通常会

say('foo';);   # a list with one element and the empty list 
# OUTPUT: «(foo)()␤» 
say(('foo';)); # no list, just the string "foo" 
# OUTPUT: «foo␤»

因为分号兼作语句终止符,所以它将在顶层使用时结束字面量列表,而是创建一个语句列表。如果你想在括号内创建一个语句列表,在括号前使用一个符号。

say so (42eqv $(my $a = 42$a;);
# OUTPUT: «True␤» 
say so (42,42eqv (my $a = 42$a;);
# OUTPUT: «True␤»

可以使用下标从列表中提取单个元素。列表的第一个元素位于索引号零处。

say (12)[0];  # says 1 
say (12)[1];  # says 2 
say (12)[2];  # says Nil 
say (12)[-1]; # Error 
say ((<a b>,<c d>),(<e f>,<g h>))[1;0;1]; # says "f" 

@ 符号§

在 Raku 中,名称带有 @ 符号的变量预计包含某种列表状对象。其他变量也可能包含这些对象,但 @ 符号变量始终包含这些对象,并且预计会扮演这个角色。

默认情况下,当你将List分配给 @ 符号变量时,你将创建一个Array。这些将在下面描述。如果你想使用 @ 符号变量直接引用List对象,可以使用 := 进行绑定。

my @a := 123;

@ 符号变量像列表一样工作的一种方式是始终支持位置下标。绑定到 @ 符号值的任何内容都必须支持Positional角色,这保证了这一点会失败。

my @a := 1# Type check failed in binding; expected Positional but got Int

重置列表容器§

要从位置容器中删除所有元素,请将Empty、空列表 () 或空列表的Slip分配给容器。

my @a = 123;
@a = ();
@a = Empty;
@a = |();

迭代§

所有列表都可以迭代,这意味着按顺序从列表中获取每个元素,并在最后一个元素后停止。

for 123 { .say }  # OUTPUT: «1␤2␤3␤»

单参数规则§

它是将传递给迭代器(如 for)的参数集视为单个参数而不是多个参数的规则;也就是说 some-iterator( a, b, c, ...) 将始终被视为 some-iterator( list-or-array(a, b, c))。在这个例子中

my @list = [ (123),
             (12, ),
             [<a b c>, <d e f>],
             [[1]] ];
 
for @list -> @element {
    say "{@element} → {@element.^name}";
    for @element -> $sub-element {
        say $sub-element;
    }
}
# OUTPUT: 
#1 2 3 → List 
#1 
#2 
#3 
#1 2 → List 
#1 
#2 
#a b c d e f → Array 
#(a b c) 
#(d e f) 
#1 → Array 
#1 

由于 for 收到的只是一个参数,因此它将被视为要迭代的元素列表。经验法则是如果有逗号,它前面的任何内容都是一个元素,因此创建的列表成为单个元素。这种情况发生在用逗号分隔的两个数组中,它是我们在这个例子中迭代的Array中的第三个元素。一般来说,引用上面链接的文章,单参数规则... 使行为符合程序员的预期

这条规则等同于说迭代器的参数不会扁平化,不会取消容器化,并且会表现得好像只有一个参数被处理了,无论该参数的形状如何。

my @a = 1,2.say for @a|@a;     # OUTPUT: «[1 2]␤1␤2␤» 
my @a = 1,2.say for $[@a|@a ]; # OUTPUT: «[[1 2] 1 2]␤» 

在第二种情况下,单个参数是一个单个元素,因为我们已经将数组细化了。单参数规则有一个例外在概要中提到:具有单个元素的列表或数组将被扁平化。

my @a = 1,2.say for [[@a ]];     # OUTPUT: «1␤2␤» 

在使用尾随逗号的情况下,结果可能有点令人惊讶。

my @a = 1,2.say for @a,;         # OUTPUT: «[1 2]␤» 

逗号运算符实际上是构建一个具有单个元素的高阶List,该元素也是一个List。所以并不那么令人惊讶。由于它只有一个元素,任何高阶列表也将像上面一样被扁平化。

my @a = 1,2.say for [@a,];       # OUTPUT: «[1 2]␤» 

测试元素§

要测试ListArray中的元素,可以使用"是...的元素"Set运算符。

my @a = <foo bar buzz>;
say 'bar' (elem) @a;    # OUTPUT: «True␤» 
say 'bar'  @a;         # same, using unicode version of operator

这等同于

'bar' (elem) @a.Set;    # convert the array to a Set first 

除了,如果可能,它实际上不会进行转换。

它基本上使用 === 中缀运算符将值与数组中的每个元素进行比较。如果你想使用其他方式比较值,你可能应该使用 first

序列§

并非所有列表都是天生就充满元素的。有些只创建它们被要求的那么多元素。这些被称为序列,它们是 Seq 类型。碰巧的是,循环返回 Seq

(loop { 42.say })[2]  # OUTPUT: «42␤42␤42␤»

因此,在 Raku 中拥有无限列表是可以的,只要你永远不要向它们索要所有元素。在某些情况下,你可能还想避免询问它们的长度 - 如果 Raku 知道一个序列是无限的,它会尝试返回 Inf,但它并不总是能知道。

这些列表可以使用 ... 运算符构建,该运算符使用各种生成表达式构建延迟列表。

虽然 Seq 类确实提供了一些位置下标,但它没有提供 Positional 的完整接口,因此 @ 标记的变量可能 **不能** 绑定到 Seq,尝试这样做会导致错误。

my @s := <a b c>.SeqCATCH { default { say .^name' '.Str } }
# OUTPUT: «X::TypeCheck::Binding Type check failed in binding; expected Positional but got Seq ($(("a", "b","c").Seq))␤»

这是因为 Seq 在你使用完值后不会保留它们。如果你有一个非常长的序列,这将是一个有用的行为,因为你可能希望在使用完值后将它们丢弃,这样你的程序就不会占用内存。例如,在处理一个包含一百万行的文件时

for 'filename'.IO.lines -> $line {
    do-something-with($line);
}

你可以确信,除非你显式地将这些行存储在某个地方,否则整个文件内容不会留在内存中。

另一方面,你可能希望在某些情况下保留旧值。可以在 List 中隐藏一个 Seq,它仍然是延迟的,但会记住旧值。这是通过调用 .list 方法完成的。由于这个 List 完全支持 Positional,你可以直接将其绑定到 @ 标记的变量。

my @s := (loop { 42.say }).list;
@s[2]; # says 42 three times 
@s[1]; # does not say anything 
@s[4]; # says 42 two more times

你也可以使用 .cache 方法而不是 .list,具体取决于你希望如何处理引用。有关详细信息,请参阅 Seq

使用 .iterator§

所有列表都混合了 Iterator 角色,因此它们都有一个 .iterator 方法,可以用来更精细地控制列表。例如,我们可以这样使用它

my @multiples-of-five = 0,5,10 … 500;
my $odd-iterator = @multiples-of-five.iterator;
my $odd;
repeat {
    $odd-iterator.skip-one;
    $odd = $odd-iterator.pull-one;
    say "→ $odd";
} until $odd.Str eq IterationEnd.Str;

我们没有像在 for 循环中那样隐式地使用迭代器,而是显式地将其分配给 $odd-iterator 变量,以便只遍历序列中的奇数元素。这样,我们可以使用 .skip-one 跳过偶数元素。我们必须显式地测试终止,我们在 until 表达式中这样做。当没有剩余的元素可以迭代时,$odd 将具有值 IterationEnd。有关可用的方法和函数,请参阅 Iterator

滑块§

有时你希望将列表的元素插入另一个列表中。这可以通过一种称为 Slip 的特殊类型的列表来完成。

say (1, (23), 4eqv (1234);         # OUTPUT: «False␤» 
say (1Slip.new(23), 4eqv (1234); # OUTPUT: «True␤» 
say (1slip(23), 4eqv (1234);     # OUTPUT: «True␤»

另一种创建 Slip 的方法是使用 | 前缀运算符。请注意,它的优先级比逗号高,因此它只影响单个值,但与上述选项不同,它会破坏 Scalar

say (1|(23), 4eqv (1234);        # OUTPUT: «True␤» 
say (1|$(23), 4eqv (1234);       # OUTPUT: «True␤» 
say (1slip($(23)), 4eqv (1234);  # OUTPUT: «True␤»

延迟列表§

ListSeqArray 以及任何其他实现 Iterator 角色的类都可以是延迟的,这意味着它们的价值是按需计算的,并存储以供以后使用。创建延迟对象的一种方法是使用 gather/take序列运算符。你也可以编写一个实现 Iterator 角色的类,并在调用 is-lazy 时返回 True。请注意,某些方法(如 elems)不能在延迟列表上调用,否则会导致抛出 Exception

# This array is lazy and its elements will not be available 
# until explicitly requested. 
 
my @lazy-array = lazy 111121 ... 10**100;
say @lazy-array.is-lazy;     # OUTPUT: «True␤» 
say @lazy-array[];           # OUTPUT: «[...]␤» 
 
# Once all elements have been retrieved, the list 
# is no longer considered lazy. 
 
my @no-longer-lazy = eager @lazy-array;  # Forcing eager evaluation 
say @no-longer-lazy.is-lazy;             # OUTPUT: «False␤» 
say @no-longer-lazy[];
# OUTPUT: (sequence starting with «[1 11 121» ending with a 300 digit number) 

在上面的例子中,@lazy-array 是一个 Array,它在构造过程中被设置为 lazy。调用 is-lazy 方法实际上调用了由角色 Iterator 混合进来的方法,由于它起源于一个惰性列表,所以它本身也是惰性的。

惰性 Seq 的一个常见用例是处理无限的数字序列,这些数字的值尚未计算,也不能全部计算。列表中的特定值只有在需要时才会被计算。

my  $l := 1248 ... Inf;
say $l[0..16];
# OUTPUT: «(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536)␤» 

您可以轻松地将惰性对象分配给其他对象,从而保留它们的惰性。

my  $l := 1248 ... Inf# This is a lazy Seq. 
my  @lazy-array = $l;
say @lazy-array[10..15]; # OUTPUT: «(1024 2048 4096 8192 16384 32768)␤» 
say @lazy-array.is-lazy# OUTPUT: «True␤»

不变性§

到目前为止,我们讨论过的列表(ListSeqSlip)都是不可变的。这意味着您不能从它们中删除元素,也不能重新绑定现有元素。

(123)[0]:delete# Error Can not remove elements from a List 
(123)[0:= 0;   # Error Cannot use bind operator with this left-hand side 
(123)[0= 0;    # Error Cannot modify an immutable Int 

但是,如果任何元素被包装在一个 Scalar 中,您仍然可以更改该 Scalar 指向的值。

my $a = 2;
(1$a3)[1= 42;
$a.say;            # OUTPUT: «42␤»

也就是说,只有列表结构本身——有多少个元素以及每个元素的标识——是不可变的。不可变性不会传染到元素的标识之外。

列表上下文§

到目前为止,我们主要处理的是中性上下文中的列表。实际上,列表在语法层面上非常依赖于上下文。

列表赋值上下文§

当一个列表(或将要转换为列表的东西)出现在赋值到 @ 符号变量的右侧时,它将被“急切地”评估。这意味着一个 Seq 将被迭代,直到它不能再生成任何元素,例如。这是您不想放置无限列表的地方之一,以免您的程序挂起,最终耗尽内存。

my @divisors = (gather {
    for <2 3 5 7> {
        take $_ if 70 %% $_;
    }
});
say @divisors# OUTPUT: «[2 5 7]␤»

The gather 语句 创建一个惰性列表,该列表在分配给 @divisors 时被急切地评估。

扁平化“上下文”§

当您有一个包含子列表的列表,但您只想要一个扁平列表时,您可以扁平化该列表以生成一个值序列,就像所有括号都被删除一样。无论括号嵌套多少层,这都适用。

say (1, (2, (34)), 5).flat eqv (12345# OUTPUT: «True␤»

这与其说是一个语法上的“上下文”,不如说是一个迭代过程,但它具有上下文的表象。

请注意,围绕列表的 Scalar 将使其不受扁平化的影响。

for (1, (2$(34)), 5).flat { .say } # OUTPUT: «1␤2␤(3 4)␤5␤»

…但是一个 @ 符号变量将泄漏其元素。

my @l := 2, (34);
for (1@l5).flat { .say };      # OUTPUT: «1␤2␤3␤4␤5␤» 
my @a = 2, (34);                 # Arrays are special, see below 
for (1@a5).flat { .say };      # OUTPUT: «1␤2␤(3 4)␤5␤»

参数列表(捕获)上下文§

当一个列表作为函数或方法调用的参数出现时,特殊的语法规则起作用:该列表立即被转换为 Capture。一个 Capture 本身具有一个 List (.list) 和一个 Hash (.hash)。任何键没有被引用或没有被括号括起来的 Pair 字面量都不会进入 .list。相反,它们被认为是命名参数,并被压入 .hash。有关此处理的详细信息,请参阅 Capture

考虑以下从 List 创建新 Array 的方法。这些方法将 List 放置在参数列表上下文中,因此 Array 只包含 12,而不包含 Pair :c(3),该 Pair 被忽略。

Array.new(12:c(3));
Array.new: 12:c(3);
new Array: 12:c(3);

相反,这些方法不会将 List 放置在参数列表上下文中,因此所有元素,即使是 Pair :c(3),都被放置在 Array 中。

Array.new((12:c(3)));
(12:c(3)).Array;
my @a = 12:c(3); Array.new(@a);
my @a = 12:c(3); Array.new: @a;
my @a = 12:c(3); new Array: @a;

在参数列表上下文中,应用于 Positional| 前缀运算符将始终将列表元素作为位置参数滑入 Capture,而应用于 Associative| 前缀运算符将将对作为命名参数滑入。

my @a := 2"c" => 3;
Array.new(1|@a4);    # Array contains 1, 2, :c(3), 4 
my %a = "c" => 3;
Array.new(1|%a4);    # Array contains 1, 4

切片索引上下文§

切片下标 中的 List 的角度来看,它只是不显眼:因为 副词 附加在 ] 之后,所以切片内部不是参数列表,并且不会对对形式进行特殊处理。

大多数 Positional 类型将对切片索引的每个元素强制执行整数强制转换,因此在那里出现的对将生成错误,无论如何。

(123)[12:c(3)] # OUTPUT: «Method 'Int' not found for invocant of class 'Pair'␤» 

...但是,这完全取决于类型 - 如果它定义了对的顺序,它可能会认为:c(3)是一个有效的索引。

切片内的索引通常不会自动扁平化,但子列表通常也不会强制转换为Int。相反,列表结构保持不变,导致嵌套的切片操作,在结果中复制结构

say ("a""b""c")[(12), (01)] eqv (("b""c"), ("a""b")) # OUTPUT: «True␤»

切片也可以使用半列表跨多个维度进行,半列表是通过分号分隔的切片列表

my @sliceable = [[ ^10 ], ['a'..'h'], [''..'']];
say @sliceable^34..6 ]; # OUTPUT: «(4 5 6 e f g Ⅴ Ⅵ Ⅶ)␤»

它从前三个维度(^3)中选择第 4 到第 6 个元素。

范围作为切片§

一个Range 是一个包含下界和上界的容器,其中任何一个都可能被排除。使用Range 生成切片将包括边界之间的任何索引,尽管无限范围将截断不存在的元素。具有排除上界的无限范围(例如 0..^Inf)仍然是无限的,并将到达所有元素。

my @a = 1..5;
say @a[0..2];     # OUTPUT: «(1 2 3)␤» 
say @a[0..^2];    # OUTPUT: «(1 2)␤» 
say @a[0..*];     # OUTPUT: «(1 2 3 4 5)␤» 
say @a[0..^*];    # OUTPUT: «(1 2 3 4 5)␤» 
say @a[0..Inf-1]; # OUTPUT: «(1 2 3 4 5)␤»

请注意,当上界是WhateverCode 而不是仅仅是Whatever 时,范围不是无限的,而是成为一个Callable,生成Range。这是..运算符的正常行为。下标运算符[] 评估WhateverCode,提供列表的.elems 作为参数,并使用生成的范围进行切片

say @a[0..*-1];   # OUTPUT: «(1 2 3 4 5)␤» 
say @a[0..^*-1];  # OUTPUT: «(1 2 3 4)␤» 
# Produces 0..^2.5 as the slice range 
say @a[0..^*/2];  # OUTPUT: «(1 2 3)␤» 

注意,0..^*0..^*+0 在下标中表现一致,尽管一个是无限范围,另一个是生成范围的 WhateverCode,但 0..*+0 会给你一个额外的尾随Nil,因为与无限范围 0..* 不同,它不会截断。

数组构造函数上下文§

在数组字面量内,初始化值的列表不在捕获上下文中,只是一个普通列表。但是,它会像赋值一样被急切地评估。

say so [ 12:c(3) ] eqv Array.new((12:c(3))); # OUTPUT: «True␤» 
[while $++ < 2 { 42.say43 }].map: *.say;           # OUTPUT: «42␤42␤43␤43␤» 
(while $++ < 2 { 42.say43 }).map: *.say;           # OUTPUT: «42␤43␤42␤43␤»

这让我们谈到了数组...

数组§

Array 与列表有三个主要区别:它们的元素可以是类型化的,它们会自动将元素逐项列出,并且它们是可变的。否则,它们是列表,并且在任何接受列表的地方都被接受。

say Array ~~ List     # OUTPUT: «True␤»

它们的不同之处在于,在处理数组时,有时可能难以保持惰性或处理无限序列。

类型化§

数组可以是类型化的,这样它们的槽在每次被赋值时都会执行类型检查。一个只允许Int 值被赋值的数组类型为 Array[Int],可以使用 Array[Int].new 创建一个。如果你打算只将一个@-sigil 变量用于此目的,你可以在声明它时通过指定元素的类型来更改它的类型

my Int @a = 123;              # An Array that contains only Ints 
# the same as 
my @a of Int = 123;           # An Array of Ints 
my @b := Array[Int].new(123); # Same thing, but the variable is not typed 
my @b := Array[Int](123);     # Rakudo shortcut for the same code 
say @b eqv @a;                    # says True. 
my @c = 123;                  # An Array that can contain anything 
say @b eqv @c;                    # says False because types do not match 
say @c eqv (123);             # says False because one is a List 
say @b eq @c;                     # says True, because eq only checks values 
say @b eq (123);              # says True, because eq only checks values 
 
@a[0= 42;                       # fine 
@a[0= "foo";                    # error: Type check failed in assignment 

在上面的例子中,我们将一个类型化的数组对象绑定到一个@-sigil 变量,该变量没有指定类型。反过来则行不通 - 你不能将一个类型错误的数组绑定到一个类型化的@-sigil 变量

my @a := Array[Int].new(123);     # fine 
@a := Array[Str].new("a""b");       # fine, can be re-bound 
my Int @b := Array[Int].new(123); # fine 
@b := Array.new(123);             # error: Type check failed in binding 

在处理类型化数组时,重要的是要记住它们是名义类型化的。这意味着数组的声明类型才是最重要的。给定以下子声明

sub mean(Int @a{
    @a.sum / @a.elems
}

传递 Array[Int] 的调用将成功

my Int @b = 135;
say mean(@b);                       # @b is Array[Int] 
say mean(Array[Int].new(135));  # Anonymous Array[Int] 
say mean(my Int @ = 135);       # Another anonymous Array[Int] 

但是,以下调用都会失败,因为传递了一个未类型化的数组,即使该数组恰好在传递时包含 Int 值

my @c = 135;
say mean(@c);                       # Fails, passing untyped Array 
say mean([135]);                # Same 
say mean(Array.new(135));       # Same again 

请注意,在任何给定的编译器中,可能存在一些巧妙的、幕后的方法来绕过数组上的类型检查,因此在处理不受信任的输入时,最好在需要的地方执行额外的类型检查

for @a -> Int $i { $_++.say };

但是,只要你在受信任的代码区域内坚持使用正常的赋值操作,这将不会成为问题,类型检查错误将在赋值到数组时立即发生,如果它们不能在编译时被捕获。Raku 中提供的用于操作列表的所有核心函数都不应该生成任何奇怪的类型化数组。

不存在的元素(当被索引时)或被分配了 Nil 的元素将采用默认值。这个默认值可以通过 is default 特性在每个变量的基础上进行调整。注意,一个未类型化的 @ 符号变量的元素类型为 Mu,但它的默认值是未定义的 Any

my @a;
@a.of.raku.say;                 # OUTPUT: «Mu␤» 
@a.default.raku.say;            # OUTPUT: «Any␤» 
@a[0].say;                      # OUTPUT: «(Any)␤» 
my Numeric @n is default(Real);
@n.of.raku.say;                 # OUTPUT: «Numeric␤» 
@n.default.raku.say;            # OUTPUT: «Real␤» 
@n[0].say;                      # OUTPUT: «(Real)␤»

固定大小数组§

为了限制 Array 的维度,在数组容器名称之后方括号内用 ,; 分隔提供维度(如果有多个维度);这些也被称为“整形”数组。这种 Array 的值将默认为 Any。可以通过 shape 方法在运行时访问形状。

my @a[2,2];
say @a.raku;
# OUTPUT: «Array.new(:shape(2, 2), [Any, Any], [Any, Any])␤» 
say @a.shape;         # OUTPUT: «(2 2)␤» 
my @just-three[3= <alpha beta kappa>;
say @just-three.raku;
# OUTPUT: «Array.new(:shape(3,), ["alpha", "beta", "kappa"])␤»

形状将控制每个维度可以分配的元素数量

my @just-two[2= <alpha beta kappa>;
# Will throw exception: «Index 2 for dimension 1 out of range (must be 0..1)» 

对固定大小 Array 的赋值将把列表的列表提升为数组的数组(在此过程中使其可变)。

my @a[2;2= (1,23,4);
say @a.Array# OUTPUT: «[1 2 3 4]␤» 
@a[1;1= 42;
say @a.raku;
# OUTPUT: «Array.new(:shape(2, 2), [1, 2], [3, 42])␤»

如第三条语句所示,您也可以直接对整形数组中的元素进行赋值。注意:第二条语句仅从 2018.09 版本开始生效。

从 6.d 版本开始,enum 也可以用作形状参数

enum Cards <Trump Ace Deuce Trey>;
my @cards[Deuce;Deuce];
say @cards.shape# OUTPUT: «(Deuce Deuce)␤» 

逐项列举§

在大多数情况下,Array 由多个槽位组成,每个槽位包含一个正确类型的 Scalar。每个这样的 Scalar 反过来包含该类型的值。当初始化、赋值或构造数组时,Raku 会自动进行类型检查并创建标量来包含它们。

这实际上是 Raku 列表处理中最难理解的部分之一。

首先,请注意,由于数组中的逐项列举是假设的,它本质上意味着如果您没有自己添加 $(…),那么您分配给数组的所有内容都会被放在 $(…) 中。另一方面,Array.raku 不会添加 $ 来显式显示标量,这与 List.raku 不同。

((12), $(34)).raku.say# says "((1, 2), $(3, 4))" 
[(12), $(34)].raku.say# says "[(1, 2), (3, 4)]" 
                            # ...but actually means: "[$(1, 2), $(3, 4)]"

人们决定所有这些额外的美元符号和括号对用户来说更像是一种视觉上的障碍,而不是一种好处。基本上,当您看到方括号时,请记住那些看不见的美元符号。

其次,请记住,这些看不见的美元符号也防止扁平化,因此您不能真正使用对 flat.flat 的正常调用来扁平化数组内部的元素。

((12), $(34)).flat.raku.say# OUTPUT: «(1, 2, $(3, 4)).Seq␤» 
[(12), $(34)].flat.raku.say# OUTPUT: «($(1, 2), $(3, 4)).Seq␤»

由于方括号本身不防止扁平化,因此您仍然可以使用 flat 将元素从数组中溢出到周围的列表中。

(0, [(12), $(34)], 5).flat.raku.say# OUTPUT: «(0, $(1, 2), $(3, 4), 5).Seq␤»

…但是,元素本身保持完整。

如果您有深度嵌套的数组,而用户希望获得扁平化数据,这可能会让您的数据用户感到困扰。目前,他们必须手动深度映射结构来撤消嵌套。

say gather [0, [(12), [34]], $(56)].deepmap: *.take# OUTPUT: «(0 1 2 3 4 5 6)␤»

…Raku 的未来版本可能会找到一种更简单的方法。但是,当非逐项列举列表就足够时,不从函数中返回数组或逐项列举列表,这应该被视为对用户的礼貌。

  • 当您希望始终与周围列表合并时,请使用 Slip

  • 当您希望让用户更容易扁平化时,请使用非逐项列举列表。

  • 使用逐项列举列表来保护用户可能不想扁平化的内容。

  • 如果合适,请使用 Array 作为非逐项列举的逐项列举列表。

  • 如果用户希望在不先复制的情况下修改结果,请使用 Array

数组的所有元素都是逐项列举的(在 Scalar 容器中),这更像是一种君子协定,而不是一个普遍执行的规则,而且它比类型化数组中的类型检查执行得更少。请参阅下面关于绑定到数组槽位的部分。

字面数组§

字面 Array 是用方括号内的 List 构造的。该 List 被急切地迭代(如果可能,在编译时),并且其中的值都经过类型检查和逐项列举。方括号本身在扁平化时会将元素溢出到周围的列表中,但元素本身不会溢出,因为它们是逐项列举的。

可变性§

与列表不同,Array 是可变的。元素可以被删除、添加或更改。

my @a = "a""b""c";
@a.say;                  # OUTPUT: «[a b c]␤» 
@a.pop.say;              # OUTPUT: «c␤» 
@a.say;                  # OUTPUT: «[a b]␤» 
@a.push("d");
@a.say;                  # OUTPUT: «[a b d]␤» 
@a[13= "c""c";
@a.say;                  # OUTPUT: «[a c d c]␤»

赋值§

将列表赋值给 Array 是急切的。列表将被完全评估,并且不应该无限,否则程序可能会挂起。类似地,将列表赋值给 Array 的切片也是急切的,但仅限于请求的元素数量,这可能是有限的。

my @a;
@a[012= (loop { 42 });
@a.say;                     # OUTPUT: «[42 42 42]␤»

在赋值期间,将对每个值进行类型检查,以确保它是 Array 允许的类型。任何 Scalar 将从每个值中剥离,并用新的 Scalar 包裹它。

绑定§

可以使用与 $ 符号变量相同的方式绑定单个 Array 槽。

my $b = "foo";
my @a = 123;
@a[2:= $b;
@a.say;          # OUTPUT: «[1 2 "foo"]␤» 
$b = "bar";
@a.say;          # OUTPUT: «[1 2 "bar"]␤»

... 但强烈建议不要将 Array 槽直接绑定到值。如果您这样做,请期待内置函数带来的意外情况。只有在需要知道值和 Scalar 包裹值之间区别的可变容器,或者对于非常大的 Array(无法使用原生类型数组)时,才会这样做。具有绑定槽的数组永远不应该提供给不知情的用户。