列表一直是計算機的核心部分,因為之前有計算機,在這段時間里,許多惡魔占據了他們的細節。 它們實際上是 Perl 6 設計中最難的部分之一,但是通過堅持和耐心,Perl 6 已經使用了一個優雅的系統來處理它們。
Literal Lists
字面上的列表用逗號和分號不是用圓括號創建,因此:
1, 2 # This is two-element list
(1, 2) # This is also a List, in parentheses
(1; 2) # same List
(1) # This is not a List, just a 1 in parentheses
(1,) # This is a one-element List
括號可用于標記列表
的開頭和結尾,因此:
(1, 2), (1, 2) # This is a list of two lists.
多維字面上的列表
是通過逗號和分號組合而成的。 它們可以在常規參數列表和下標中使用。
say so (1,2; 3,4) eqv ((1,2), (3,4));
# OUTPUT?True
?
say('foo';); # a list with one element and the empty list
# OUTPUT?(foo)()
?
單個元素可以使用下標從列表中拉出。 列表的第一個元素的索引號為零:
say (1, 2)[0]; # says 1
say (1, 2)[1]; # says 2
say (1, 2)[2]; # says Nil
say (1, 2)[-1]; # Error
say (1, 2)[*-1]; # 2
The @ sigil
Perl 6 中名稱為 @ 符號的變量應該包含某種類似列表的對象。 當然,其他變量也可能包含這些對象,但是 @-sigiled
變量總是這樣,并且期望它們作用于該部分。
默認情況下,當您將列表
分配給 @-sigiled
變量時,您將創建一個數組
。 這些在下面描述。 如果你想把一個真實的的 List
放到一個 @ -sigiled 變量中,你可以用 :=
綁定代替。
my @a := 1, 2, 3;
將列表的列表賦值給 @-sigiled 變量不提供相同的快捷方式。 在這種情況下,外部 List 成為數組的第一個元素。
my @a = (1,2; 3,4);
say @a.flat;
# OUTPUT?((1 2) (3 4))
?
@a := (1,2; 3,4);
say @a.flat;
# OUTPUT?((1 2 3 4)
?
@_sigiled 變量像列表一樣的方式之一是總是支持位置下標。 任何綁定到 @-sigiled 值的東西都必須支持 Positional 角色,這保證了:
my @a := 1; # Type check failed in binding; expected Positional but got Int
# 但是
my @a := 1,; # (1)
Reset a List Container
要從 Positional 容器中刪除所有元素,請將 Empty,空列表 ()
或空列表的 Slip
賦值給容器。
my @a = 1, 2, 3;
@a = ();
@a = Empty;
@a = |();
Iteration
所有的列表都可以被迭代,這意味著從列表中按順序拿出每個元素并在最后一個元素之后停止:
for 1, 2, 3 { .say } # says 1, then says 2, then says 3
Testing for Elements
要測試元素將 List
或 Array
轉換為 Set 或使用 Set 運算符。
my @a = <foo bar buzz>;
say @a.Set<bar buzz>; # (True True)
say so 'bar' ∈ @a; # True
Sequences
不是所有的列表生來都充滿元素。 有些只創建他們被要求的盡可能多的元素。 這些稱為序列,其類型為 Seq
。 因為這樣發生,循環返回 Seqs
。
(loop { 42.say })[2] # says 42 three times
所以,在 Perl 6 中有無限列表是很好的,只要你從不問他們所有的元素。 在某些情況下,你可能希望避免詢問它們有多長 - 如果 Perl 6 知道一個序列是無限的,它將嘗試返回 Inf
,但它不能總是知道。
雖然 Seq
類確實提供了一些位置下標,但它不提供 Positional 的完整接口,因此 @-sigiled 變量可能不會綁定到 Seq
。
my @s := (loop { 42.say }); # Error expected Positional but got Seq
這是因為 Seq
在使用它們之后不會保留值。 這是有用的行為,如果你有一個很長的序列,因為你可能想在使用它們之后丟棄值,以便你的程序不會填滿內存。 例如,當處理一個百萬行的文件時:
for 'filename'.IO.lines -> $line {
do-something-with($line);
}
你可以確信文件的整個內容不會留在內存中,除非你明確地存儲某個地方的行。
另一方面,在某些情況下,您可能希望保留舊值。 可以在列表
中隱藏一個 Seq
,它仍然是惰性的,但會記住舊的值。 這是通過調用 .list
方法完成的。 由于此列表
完全支持 Positional
,因此可以將其直接綁定到 @-sigiled 變量上。
my @s := (loop { 42 }).list;
@s[2]; # Says 42 three times
@s[1]; # does not say anything
@s[4]; # Says 42 two more times
您還可以使用 .cache
方法代替 .list
,這取決于您希望處理引用的方式。 有關詳細信息,請參閱 Seq 上的頁面。
Slips
有時候你想把一個列表的元素插入到另一個列表中。 這可以通過一個稱為 Slip 的特殊類型的列表來完成。
say (1, (2, 3), 4) eqv (1, 2, 3, 4); # says False
say (1, Slip.new(2, 3), 4) eqv (1, 2, 3, 4); # says True
say (1, slip(2, 3), 4) eqv (1, 2, 3, 4); # also says True
另一種方法是使用 |
前綴運算符。 注意,這有一個比逗號更嚴格的優先級,所以它只影響一個單一的值,但不像上面的選項,它會打碎標量。而 slip
不會。
say (1, |(2, 3), 4) eqv (1, 2, 3, 4); # says True
say (1, |$(2, 3), 4) eqv (1, 2, 3, 4); # also says True
say (1, slip($(2, 3)), 4) eqv (1, 2, 3, 4); # says False
Lazy Lists
列表可以是惰性的,這意味著它們的值是根據需要計算的,并且存儲供以后使用。 要創建惰性列表,請使用 gather/take 或序列運算符。 您還可以編寫一個實現 Iterable 角色的類,并在調用 lazy 時返回 True
。 請注意,某些方法(如 elems
)可能會導致整個列表計算失敗,如果列表也是無限的。無限列表沒辦法知道它的元素個數。
my @l = 1,2,4,8...Inf;
say @l[0..16];
# OUTPUT?(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536)
?
Immutability
到目前為止我們談論的列表(List
,Seq
和 Slip
)都是不可變的。 這意味著您不能從中刪除元素,或重新綁定現有元素:
(1, 2, 3)[0]:delete; # Error Can not remove elements from a List
(1, 2, 3)[0] := 0; # Error Cannot use bind operator with this left-hand side
(1, 2, 3)[0] = 0; # Error Cannot modify an immutable Int
但是,如果任何元素包裹在標量中,您仍然可以更改 Scalar
指向的值:
my $a = 2;
(1, $a, 3)[1] = 42;
$a.say; # says 42
...就是說,它只是列表結構本身 - 有多少個元素和每個元素的標識 - 是不可變的。 不變性不是通過元素的身份傳染。
List Contexts
到目前為止,我們主要是在中立語境下處理列表。 實際上列表在語法層面上上下文非常敏感。
List Assignment Context
當一個列表出現在賦值給 @-sigiled 變量的右邊時,它被“熱切地”計算。 這意味著 Seq
將被迭代,直到它不能產生更多的元素。 這是你不想放置無限列表的地方之一,免得你的程序掛起,最終耗盡內存:
my $i = 3;
my @a = (loop { $i.say; last unless --$i }); # Says 3 2 1
say "take off!";
Flattening "Context"
當您的列表包含子列表,但您只想要一個平面列表時,可以展平該列表以生成一系列值,就像所有的括號被刪除了一樣。 無論括號中有多少層次嵌套,這都可以工作。
請注意,列表周圍的標量將使其免于扁平化:
for (1, (2, $(3, 4)), 5).flat { .say } # says 1, then 2, then (3 4), then 5
...但是一個 @-sigiled 變量將溢出它的元素。
my @l := 2, (3, 4);
for (1, @l, 5).flat { .say }; # says 1, then 2, then 3, then 4, then 5
my @a = 2, (3, 4); # Arrays are special, see below
for (1, @a, 5).flat { .say }; # says 1, then 2, then (3 4), then 5
Argument List (Capture) Context
當列表作為函數或方法調用的參數出現時,會使用特殊的語法規則:該列表立即轉換為 Capture
。 Capture
本身有一個 List(.list
)和一個 Hash(.hash
)。 任何鍵沒有引號的 Pair
,或者沒有括號的 Pair
字面量,永遠不會變成 .list
。 相反,它們被認為是命名參數,并且壓縮為 .hash
。 有關此處理的詳細信息,請參閱 Capture 上的頁面。
考慮從列表
中創建新數組
的以下方法。 這些方法將 List
放在參數列表上下文中,因此,Array
只包含 1 和 2,但不包含 Pair :c(3)
,它被忽略。
Array.new(1, 2, :c(3));
Array.new: 1, 2, :c(3);
new Array: 1, 2, :c(3);
相反,這些方法不會將 List
放置在參數列表上下文中,所以所有元素,甚至 Pair :c(3)
,都放置在數組
中。
Array.new((1, 2, :c(3)));
(1, 2, :c(3)).Array;
my @a = 1, 2, :c(3); Array.new(@a);
my @a = 1, 2, :c(3); Array.new: @a;
my @a = 1, 2, :c(3); new Array: @a;
在參數列表上下文中,應用于 Positional
上的 |
前綴運算符總是將列表元素slip為Capture的位置參數,而應用到 Associative
上的 |
前綴運算符會把 pairs 作為具名參數 slip 進來:
`perl6
my @a := 2, "c" => 3;
Array.new(1, |@a, 4); # Array contains 1, 2, :c(3), 4
my %a = "c" => 3;
Array.new(1, |%a, 4); # Array contains 1, 4
### Slice Indexing Context
從[切片下標](https://docs.perl6.org/language/subscripts#Slices) 中的 `List` 角度來看,只有一個顯著的地方在于它是不可見的:因為一個切片的副詞附在 `]` 后面,切片的內部**不是**參數列表,并且沒有對 pair 形式的特殊處理 。
大多數 `Positional` 類型將對切片索引的每個元素強制執行整數強制,因此那兒出現的 pairs 將生成錯誤,無論如何:
```perl6
(1, 2, 3)[1, 2, :c(3)] # Method 'Int' not found for invocant of class 'Pair'
...但是這完全取決于類型 - 如果它定義了pairs的順序,它可以考慮 :c(3)
是有效的索引。
切片內的索引通常不會自動展平,但是子列表通常不會強制為 Int
。 相反,列表結構保持不變,從而導致在結果中重復結構的嵌套 slice 操作:
say ("a", "b", "c")[(1, 2), (0, 1)] eqv (("b", "c"), ("a", "b")) # says True
Range as Slice
Range 是用于下邊界和上邊界的容器。 生成具有 Range
的切片將包括這些邊界之間的任何索引,包括邊界。 對于無限上限,我們同意數學家 Inf
等于 Inf-1
。
my @a = 1..5;
say @a[0..2]; # (1 2 3)
say @a[0..^2]; # (1 2)
say @a[0..*]; # (1 2 3 4 5)
say @a[0..^*]; # (1 2 3 4 5)
say @a[0..Inf-1]; # (1 2 3 4 5)
Array Constructor Context
在數組字面量中,初始化值的列表不在捕獲上下文中,只是一個正常的列表。 然而,正如在賦值中一樣,急切地對它求值。
[ 1, 2, :c(3) ] eqv Array.new((1, 2, :c(3))) # says True
[while $++ < 2 { 42.say; 43 }].map: *.say; # says 42 twice then 43 twice
(while $++ < 2 { 42.say; 43 }).map: *.say; # says "42" then "43"
# then "42" then "43"
它把我們帶到數組這兒來。
Arrays
數組與列表在三個主要方面不同:它們的元素可以被類型化,它們自動列出它們的元素,并且它們是可變的。 否則,它們是列表,并且在列表所在的位置被接受。
say Array ~~ List # says True
第四種更微妙的方式是,當使用數組時,有時可能更難以維持惰性或使用無限序列。
Typing
數組可以被類型化,使得它們的槽在被賦值時執行類型檢查。 只允許分配 Int
值的數組是 Array[Int]
類型,可以使用 Array[Int].new
創建一個數組。 如果你打算僅僅為了這個目的使用 @-sigiled 變量,你可以在聲明它時通過指定元素的類型來改變它的類型:
my Int @a = 1, 2, 3; # An Array that contains only Ints
my @b := Array[Int].new(1, 2, 3); # Same thing, but the variable is not typed
say @b eqv @a; # says True.
my @c = 1, 2, 3; # An Array that can contain anything
say @b eqv @c; # says False because types do not match
say @c eqv (1, 2, 3); # says False because one is a List
say @b eq @c; # says True, because eq only checks values
say @b eq (1, 2, 3); # says True, because eq only checks values
@a[0] = 42; # fine
@a[0] = "foo"; # error: Type check failed in assignment
在上面的例子中,我們將一個類型化的 Array 對象綁定到一個沒有指定類型的 @-sigil 變量上。 另一種方法不工作 - 你不能綁定一個類型錯誤的數組到一個類型化的 @-sigiled 變量上:
my @a := Array[Int].new(1, 2, 3); # fine
@a := Array[Str].new("a", "b"); # fine, can be re-bound
my Int @b := Array[Int].new(1, 2, 3); # fine
@b := Array.new(1, 2, 3); # error: Type check failed in binding
當使用類型化數組時,重要的是要記住它們是名義類型的。 這意味著數組的聲明類型是重要的。 給定以下子聲明:
sub mean(Int @a) {
@a.sum / @a.elems
}
傳遞 Array[Int]
的調用將成功:
my Int @b = 1, 3, 5;
say mean(@b); # @b is Array[Int]
say mean(Array[Int].new(1, 3, 5)); # Anonymous Array[Int]
say mean(my Int @ = 1, 3, 5); # Another anonymous Array[Int]
但是,由于傳遞一個無類型的數組,下面的調用將全部失敗,即使該數組在傳遞時恰好包含 Int 值:
my @c = 1, 3, 5;
say mean(@c); # Fails, passing untyped Array
say mean([1, 3, 5]); # Same
say mean(Array.new(1, 3, 5)); # Same again
請注意,在任何給定的編譯器中,可能有一些奇怪的,底層的方法來繞過數組上的類型檢查,因此在處理不受信任的輸入時,執行額外的類型檢查是一個很好的做法,
for @a -> Int $i { $_++.say };
然而,只要你堅持在一個信任的代碼區域內的正常賦值操作,這不會是一個問題,并且typecheck錯誤將在分配到數組時發生,如果他們不能在編譯時捕獲。 在Perl 6中提供的用于操作列表的核心功能不應該產生一個類型化的數組。
不存在的元素(當索引時)或已分配Nil的元素將采用默認值。 可以使用 is default
特征在逐個變量的基礎上調整此默認值。 請注意,無類型的@ -sigiled變量的元素類型為 Mu
,但其默認值為未定義的 Any
:
my @a;
@a.of.perl.say; # says "Mu"
@a.default.perl.say; # says "Any"
@a[0].say; # says "(Any)"
my Numeric @n is default(Real);
@n.of.perl.say; # says "Numeric"
@n.default.perl.say; # says "Real"
@n[0].say; # says "(Real)"
Fixed Size Arrays
要限制陣列
的尺寸,請提供由 ,
或 ;
在數組容器的名稱后面的括號中。 這樣一個數組
的值將默認為 Any
。 形狀可以在運行時通過 shape
方法訪問。
my @a[2,2];
dd @a;
# OUTPUT?Array.new(:shape(2, 2), [Any, Any], [Any, Any])
?
say @a.shape;
# OUTPUT?(2 2)
?
賦值到固定大小的數組將把一個列表的列表提升為數組的數組。
my @a[2;2] = (1,2; 3,4);
@a[1;1] = 42;
dd @a;
# OUTPUT?Array.new(:shape(2, 2), [1, 2], [3, 42])
?
Itemization
對于大多數用途,數組由多個槽組成,每個槽包含正確類型的標量
。 每個這樣的標量
,反過來,包含該類型的值。 當數組被初始化,賦值或構造時,Perl 6 將自動進行類型檢查值并創建標量來包含它們。
這實際上是 Perl 6 列表處理中最棘手的部分之一,以獲得牢固的理解。
首先,請注意,因為假設數組中的項目化,它本質上意味著 $(...)
被放置在您分配給數組的所有內容,如果你不把它們放在那里。 另一方面,Array.perl
不會將$顯式地顯示標量,與 List.perl
不同:
((1, 2), $(3, 4)).perl.say; # says "((1, 2), $(3, 4))"
[(1, 2), $(3, 4)].perl.say; # says "[(1, 2), (3, 4)]"
# ...but actually means: "[$(1, 2), $(3, 4)]"
它決定所有這些額外的美元符號和括號更多的眼睛疼痛比對用戶的好處。 基本上,當你看到一個方括號,記住隱形美元符號。
第二,記住這些看不見的美元符號也防止扁平化,所以你不能真正地扁平化一個數組內的元素與正常調用 flat
或 .flat
。
((1, 2), $(3, 4)).flat.perl.say; # (1, 2, $(3, 4)).Seq
[(1, 2), $(3, 4)].flat.perl.say; # ($(1, 2), $(3, 4)).Seq
由于方括號本身不會防止展平,因此您仍然可以使用平面將數組中的元素溢出到周圍的列表中。
(0, [(1, 2), $(3, 4)], 5).flat.perl.say; # (0, $(1, 2), $(3, 4), 5).Seq
...元素本身,但是,留在一塊。
這可以阻止用戶提供的數據,如果你有深嵌套的數組他們想要平面數據。 目前,他們必須手動地深度地映射結構以撤消嵌套:
say gather [0, [(1, 2), [3, 4]], $(5, 6)].deepmap: *.take; # (1 2 3 4 5 6)
...未來版本的 Perl 6 可能會找到一種使這更容易的方法。 但是,當 non-itemized 列表足夠時,不從函數返回數組或 itemized 列表,這是一個應該考慮作為好意給他們的用戶:
- 當您總是想要與周圍列表合并時使用 Slips
- 使用 non-itemized 列表,當你想讓用戶容易展平時
- 使用 itemized 列表來保護用戶可能不想展平的東西
- 使用數組作為 non-itemized 列表的 non-itemized 列表,如果合適
- 如果用戶想要改變結果而不首先復制結果,請使用數組。
事實上,數組的所有元素(在Scalar
容器中)是一個紳士的協議,而不是一個普遍強制的規則,并且在類型數組中的類型檢查不太好。 請參閱下面有關綁定到陣列插槽的部分。
Literal Arrays
字面數組是用方括號內的 List 構造的。 列表被熱切地迭代(如果可能,在編譯時),并且列表中的值每個都進行類型檢查和itemized。 在展平時, 方括號本身會將元素放入周圍的列表中,但是元素本身不會因為 itemization 化而溢出。
Mutability
與列表不同,數組是可變的。 元素可以刪除,添加或更改。
my @a = "a", "b", "c";
@a.say; # [a b c]
@a.pop.say; # says "c"
@a.say; # says "[a b]"
@a.push("d");
@a.say; # says "[a b d]"
@a[1, 3] = "c", "c";
@a.say; # says "[a c d c]"
Assigning
列表到數組的分配是急切的。 該列表將被完全求值,并且數組不應該是無限的否則程序可能掛起。 類似地,對陣列的分片的分配是急切的,但是僅僅達到所請求數量的元素,其可以是有限的:
my @a;
@a[0, 1, 2] = (loop { 42 });
@a.say; # says "[42 42 42]"
在賦值期間,每個值都將進行類型檢查,以確保它是 Array
允許的類型。 任何標量
將從每個值中剝離,一個新的標量
將被包裹。
Binding
單個數組槽可以以相同的方式綁定 $-sigiled 變量:
my $b = "foo";
my @a = 1, 2, 3;
@a[2] := $b;
@a.say; # says '[1 2 "foo"]'
$b = "bar";
@a.say; # says '[1 2 "bar"]'
...但強烈不建議將 Array 槽直接綁定到值。 如果你這樣做,預期內置函數的驚喜。 只有當需要知道值和Scalar-Wrapped值之間的差異的可變容器時,或者對于不能使用本地類型數組的非常大的Arrays,才需要執行此操作。 這樣的生物永遠不應該被傳遞回不知情的用戶。
說明: 本文使用谷歌翻譯, 有人工刪改, 語句生硬、不通之煩處請指出。