Perl 6 中列表、序列和數組

列表一直是計算機的核心部分,因為之前有計算機,在這段時間里,許多惡魔占據了他們的細節。 它們實際上是 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

要測試元素將 ListArray 轉換為 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

到目前為止我們談論的列表(ListSeqSlip)都是不可變的。 這意味著您不能從中刪除元素,或重新綁定現有元素:

(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

當列表作為函數或方法調用的參數出現時,會使用特殊的語法規則:該列表立即轉換為 CaptureCapture 本身有一個 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,才需要執行此操作。 這樣的生物永遠不應該被傳遞回不知情的用戶。

說明: 本文使用谷歌翻譯, 有人工刪改, 語句生硬、不通之煩處請指出。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容