跳轉到內容

Raku 程式設計/單頁面

來自 Wikibooks,開放世界中的開放書籍

Raku 是 Perl 程式語言的繼任者,代表了該語言的一次重大向後不相容的重寫。它是一種多正規化程式語言,用途廣泛且功能強大。本書將向讀者介紹 Raku 語言及其眾多功能。

Camelia,Raku 程式語言的吉祥物。

Raku 程式語言是 Perl 的第六個主要版本。

它旨在解決 Perl 在其悠久歷史中積累的缺陷。這些缺陷主要是由於 Perl 連續版本對向後相容性的要求造成的。這就是為什麼 Raku 是第一個向後相容的 Perl 版本的原因。

Raku 並沒有取代 Perl。與其說它是一種姊妹語言,不如說它是 Perl 的研發分支。在某種程度上,Raku 之於 Perl,就像 C++ 之於 C。雖然 C++ 是一種非常成功的程式語言,但它並沒有取代 C。

Perl 1 - 5

[編輯 | 編輯原始碼]

Perl 程式語言由 Larry Wall 於 1987 年建立,他是一名語言學家,也是 Unisys 的計算機系統管理員。Perl 是一種動態程式語言,在其大部分歷史中被認為是“指令碼語言”或命令列管理工具。但是,從版本 5 開始,Perl 成為了一種功能強大且實用的通用程式語言,在 Web 開發人員、系統管理員和業餘程式設計師中一直很受歡迎。

Perl 程式語言作為開源免費軟體專案開發,並逐漸擴充套件到版本 5 釋出,這是 Perl 的最新技術水平。在所有這些發展過程中,Perl 保持與以前版本的向後相容性。Perl 5 直譯器可以讀取、理解和執行(大部分)用 Perl 1、Perl 2、Perl 3 和 Perl 4 編寫的程式。不幸的是,這使得 Perl 5 直譯器內部變得雜亂無章,使許多程式設計任務變得比實際需要的更難。

另一個絆腳石是 Perl 5 語言規範;它根本不是規範。Perl 直譯器本身就是標準:直譯器的行為就是 Perl 的“標準”行為。唯一能夠複製 Perl 語言所有奇怪和特殊行為的方法是僅使用該標準軟體。

插曲:2000 年的 Jon Orwant 扔杯子事件

[編輯 | 編輯原始碼]

到 2000 年,很明顯 Perl 需要注入生命

"[P5P/Perl 大會] 會議最初是 Chip Salzenberg、Jarkko Hietaniemi、Elaine Ashton、Tim Bunce、Sarathy、Nick Ing-Simmons、Larry Wall、Nat Torkington、brian d foy 和 Adam Turoff 的聚會,他們聚在一起起草了一份類似憲法的檔案,因為社群似乎正在分裂。Jon 遲到了會議,發現我們正在討論社群,他開始扔東西來表達他對 perl 本身停滯不前,甚至可能消亡的不滿,並認為我們應該討論如何復興 Perl。據我後來瞭解,杯子事件是事先策劃好的。所以,它已經是一個既成事實,但狂怒是它的曝光。" [1]

Perl 6 的時機已經成熟:從頭開始重寫 Perl 語言。為了解決語言的根本問題,並新增必要的新的功能,放棄了與 Perl 5 的相容性。因此,它與 Perl 5 完全不同,但同時又明顯屬於同一個“語言家族”。與隨著時間的推移而有機發展的 Perl 5 不同,Perl 6 從一組規範開始,並在多個獨立且平等的實現中例項化。

Perl 6 開始了一段漫長的社群參與和 RFC 流程。社群成員被要求為新語言貢獻想法和建議。Larry 稽核了這些建議,保留了好的建議,刪除了不好的建議,並試圖以統一的方式將所有建議整合在一起。Perl 5 曾因“hacky”和不一致而受到批評,因此 Perl 6 應該從一開始就避免這種情況。一旦所有建議都被統計和討論,Larry 釋出了一系列設計文件,稱為Apocalypse。每個 Apocalypse 的編號大致對應於“Programming Perl”一書中的一章,旨在揭示在 Perl 6 設計中考慮的概念和權衡。從這些文件(內容缺乏具體細節)中,Damian Conway 製作了一系列相應的解釋性文件,稱為Exegeses。Apocalypse 揭示了一些設計,而 Exegeses 則用日常程式設計師編寫的程式碼來解釋這些設計對日常程式設計師意味著什麼。後來,隨著設計日趨成熟,建立了稱為Synopses 的設計規範,以綜合和記錄 Perl 6 的設計。Synopses 目前是 Perl 6 語言的官方設計文件。

在 2019 年 10 月,Perl 6 社群投票決定將名稱更改為 Raku。

Raku 哲學

[編輯 | 編輯原始碼]

Perl 一直以來都是一種靈活且功能強大的程式語言。Perl 團隊最重要的口號之一是多種方法,任君選擇(TIMTOWTDI,發音為“Tim Toady”)。Raku 是一種非常靈活的語言,它結合了多種不同的程式設計正規化,以支援各種程式設計師和不同的程式設計任務。由於這種 TIMTOWTDI 哲學,Raku 是一種非常龐大的程式語言,擁有許多不同的功能和能力。

換句話說,Perl 為你提供了充足的繩索,但你必須小心不要被它絆倒。Raku 中存在著許多偉大的想法,但並非所有想法對所有程式設計任務都有用。此外,Raku 中存在許多可能在程式設計界不被視為“最佳實踐”的事情。重要的是要學習如何在 Raku 中編寫某些內容,以及何時以某些方式編寫內容。如果沒有這些知識,程式很容易淪為毫無意義的、不可讀的亂碼。

在本書中,我們將嘗試向您展示一些最佳實踐,並嘗試討論每個功能在哪些地方有用,以及在哪些地方沒有用。

Pugs
是第一個或多或少可用的 Raku 實現。它是由 Audrey Tang 用 Haskell 編寫的。現在它主要與歷史興趣相關。
Niecza
使用 .net framework 實現的 Raku。
Rakudo
Raku 的領先的高階實現。它是自託管的,這意味著它主要用 Raku 和 Raku 的子語言:nqp 編寫。它針對多個程序虛擬機器:Parrot、JVM、MoarVM,以及未來可能的其他虛擬機器(JavaScript、Lua 等)。

截至 2014 年 4 月,MoarVM 上的 Rakudo 是最有希望的實現。它完全是免費的開源軟體,並使用專門為 Raku 設計的虛擬機器。


Pugs 和 Parrot

[編輯 | 編輯原始碼]

經過長時間的語言設計,是時候開始建立新語言的實現。為了避免 Perl 的問題,最初的組織者決定在後端執行引擎和前端語言解析器之間建立更好的分離。經過多次討論,Parrot 虛擬機器 專案啟動,旨在為 Raku 等動態語言建立虛擬機器。Parrot 迅速發展,成為獨立於 Raku 的專案,轉而成為所有動態語言的虛擬機器。由於 Raku 規模龐大,野心勃勃,任何能夠支援它的虛擬機器也能很好地支援許多其他動態語言。

Perl 駭客 Audrey Tang 使用 Haskell 程式語言構建了 Raku 的參考實現。此實現被稱為 Pugs,並用作語言設計師正在開發的許多想法的測試平臺。來自 Pugs 團隊的反饋幫助塑造了語言設計,而語言設計中的更改又導致了 Pugs 的修改。這是一種有用且有益的關係,尤其是在當時沒有其他實現處於如此高的開發狀態的情況下。

STD.pm、STD_blue 和 ELF

[編輯 | 編輯原始碼]

Raku 的“官方”語法將用 Raku 本身編寫。這是因為 Raku 被設計為在當時擁有任何現有語言中最先進的語法引擎之一。對於如此先進的語言,沒有比該語言本身更好的語法實現選擇。STD.pm 被建立為標準的 Raku 語法,並且在各種實現之間發生衝突時仍然被參考。

STD_red 是使用 Ruby 程式語言實現的 Raku 語法。STD_blue 是 STD.pm 的一個更新的編譯器,用 Perl 編寫。

ELF 是 Raku 的一個自舉實現,它使用 STD_blue 將 Raku 程式碼編譯成 Perl 程式碼以執行。

然而,當 Audrey Tang 離開 Pugs 專案時,該專案的開發降到了最低。它仍然對測試和參考有用,但 Pugs 不再是曾經的活躍開發平臺。但是,Parrot 自那以後取得了飛躍式的進步,並且最終準備開始支援高階語言的編譯器。一個名為“Rakudo”的 Raku 專案啟動,並開始迅速發展。Rakudo 專案的一部分是 Patrick Michaud 建立的高階解析器工具,稱為 PCT(“Parrot Compiler Tools”)。PCT 是一種類似於低階 Flex 和 Bison 工具的解析器生成工具。但是,PCT 使用 Raku 語言的一個子集來編寫解析器,而不是使用 C 或 C++。這意味著 Rakudo 正在成為自託管:Rakudo 編譯器本身部分用 Raku 編寫。

有關 Rakudo 的更多資訊,請訪問 http://www.rakudo.org 網站。

Perl 6 基礎

[編輯 | 編輯原始碼]

靜態和動態程式語言

[編輯 | 編輯原始碼]

Raku 屬於一類稱為 動態語言 的程式語言。動態語言使用其資料型別可以在執行時更改的變數,並且不需要預先宣告。動態語言的替代方案是靜態語言,例如 CJava,在這些語言中,變數通常必須在使用之前宣告為特定的資料型別。

在像 C 這樣的語言或其派生語言(例如 C++C#Java)中,變數需要在使用之前預先宣告為一個型別

unsigned short int x;
x = 10;

上面的程式碼並不完全準確,因為在 C 中,您可以在宣告變數時對其進行初始化

unsigned short int x = 10;

但是,變數 x 必須在宣告之前才能使用。一旦將其宣告為 unsigned short int 型別,您就不能使用 x 來儲存其他型別的資料,例如浮點數或資料指標,至少在沒有顯式強制轉換的情況下不能這樣做

unsigned short int x;
x = 1.02;        /* Wrong! */
unsigned short int y;
x = &y;          /* Wrong! */

在像 Raku 這樣的動態程式語言中,變數可以在首次使用時自動分配,而無需顯式宣告。此外,Raku 中的變數是多型的:它們可以是整數、字串、浮點數或複雜的資料結構,例如陣列或雜湊,而無需任何強制轉換。以下是一些示例

my $x;
$x = 5;           # Integer
$x = "hello";     # String
$x = 3.1415;      # Floating Point Number

上面的示例演示了我們在本書的其餘部分將要討論的許多不同的想法。一個重要的想法是 註釋。註釋是原始碼中的註釋,旨在供程式設計師閱讀,並且被 Raku 直譯器忽略。在 Raku 中,大多數註釋用 # 符號標記,並一直持續到行尾。Raku 還具有嵌入式註釋和多行文件,我們將在稍後討論。

我們並沒有完全誠實地說明 Raku 資料。Raku 確實允許資料被賦予顯式型別,如果您想要的話。預設情況下使用的是像上面我們看到的這樣的多型資料,但您也可以宣告一個標量可能只儲存一個整數,或一個字串,或一個數字,或一個完全不同的資料項。我們將在稍後詳細討論 Raku 的顯式型別系統。現在,更容易認為 Raku 中的資料沒有顯式型別(或者更確切地說,它不需要它們)。

我們上面看到的示例還顯示了一個重要的關鍵字:mymy 用於宣告一個新的變數以供使用。我們將在稍後詳細討論 my 及其用途。

變數和符號

[編輯 | 編輯原始碼]

正如我們在上面的簡短示例中看到的,Raku 變數在其前面有符號,稱為 符號。符號有很多重要的用途,但其中一個最重要的用途是建立 上下文。Raku 有四種類型的符號,用於建立不同型別的資料。我們上面看到的 $ 符號用於 標量:單個數據值,例如數字、字串或物件引用。其他要使用的符號是 @,它表示資料的陣列, % 表示資料的雜湊,以及 & 表示子例程或可執行程式碼塊。

標量
正如我們已經看到的那樣,標量包含單個數據項,例如數字或字串。
陣列
陣列是有序資料的列表,它們使用數字索引。
雜湊
雜湊是潛在不同型別資料物件的集合,使用字串索引。
程式碼引用
程式碼引用是指向可執行程式碼結構的指標,這些結構可以像資料一樣傳遞,並在程式碼的不同位置呼叫。

我們在上面已經看到了一些標量的用法,這裡我們將展示一個稍微更全面的列表。

my $x;
$x = 42;             # Decimal Integer
$x = 0xF6;           # Hexadecimal Integer
$x = 0b1010010001;   # Binary Integer

$x = 3.1415;         # Floating Point Number
$x = 2.34E-5;        # Scientific Notation

$x = "Hello ";       # Double-Quoted String
$x = 'World!';       # Single-Quoted String
$x = q:to/EOS/;      # Heredoc string

This is a heredoc string. It starts at the "q:to"
term and continues until we reach the terminator
specified in quotes above. This is useful for
large multi-line string literals. We will talk about
heredocs in more detail later

EOS

$x = MyObject.new(); # Object Reference

標量是 Raku 中最基本和最基本的資料型別,並且可能是在您的程式中最常使用的型別。這是因為它們非常通用。

正如我們在上面提到的,陣列是有序資料物件的列表,這些物件被認為是同一型別。由於陣列是標量的列表,因此陣列中的某些元素可能是數字,而某些元素可能是字串,還有一些元素可能是完全不同的資料。但是,這通常不被認為是陣列的最佳用途。

陣列以 @ 符號為字首,可以使用 [ 方括號 ] 中的整數索引。以下是一些使用陣列的示例

my @a;
@a = 1, 2, 3;
@a = "first", "second", "third";
@a = 1.2, 3.14, 2.717;

一旦我們有了陣列,就可以使用索引符號從其中提取標量資料項

my @a, $x;
@a = "first", "second", "third";
$x = @a[0];    # first
$x = @a[1];    # second
$x = @a[2];    # third

陣列也可以是多維的

my @a;
@a[0, 0] = 1;
@a[0, 1] = 2;
@a[1, 0] = 3;
@a[1, 1] = 4;

# @a is now:
#       |1, 2|
#       |3, 4|

陣列也不必只儲存標量,它們也可以儲存任何其他資料項

my @a, @b, @c, @d, %e, %f, %g, &h, &i, &j;
@a = @b, @c, @d;
@a = %e, %f, %g;
@a = &h, &i, &j;

這可以成為一些複雜資料結構的基礎,我們將在稍後詳細討論如何組合這些結構。

雜湊在很多方面類似於陣列:它們可以包含一組物件。但是,與陣列不同,雜湊使用名稱來標識其專案,而不是使用數字。以下是一些示例

my %a = "first" => 1, "second" => 2, "third" => 3;
my $x = %a{"first"};        # 1
my $y = %a{"second"};       # 2
my $z = %a{"third"};        # 3

特殊的 => 符號類似於逗號,但它建立的是一個 。對是字串名稱和關聯資料物件的組合。雜湊有時可以被認為是成對的陣列。還要注意,雜湊使用花括號來索引其資料,而不是像陣列那樣使用方括號。

雜湊還可以使用一種稱為 自動引用 的特殊語法,以幫助簡化雜湊值的查詢。您可以使用不帶引號的尖括號 < > 來代替使用花括號和引號 {" "} 來完成相同的工作

my %a = "foo" => "first", "bar" => "second";
my $x = %a{"foo"};      # "first"
my $y = %a<bar>         # "second"

副詞語法

[編輯 | 編輯原始碼]

雜湊中的鍵值對可以使用另一種方式定義,而無需使用 => 運算子。鍵值對也可以使用 **副詞語法** 來定義。副詞語法在 Raku 中被廣泛用於提供命名資料值,因此它不僅僅適用於雜湊。"name" => data 形式的鍵值對可以用副詞語法寫成 :name(data)

my %foo = "first" => 1, "second" => 2, "third" => 3;
my %bar = :first(1), :second(2), :third(3);       # Same!

我們將在 Raku 中看到副詞的許多用法,因此現在學習它們很重要。

$_ 預設變數

[編輯 | 編輯原始碼]

Raku 使用特殊變數 $_ 作為預設變數。當沒有提供其他變數時,$_ 會接收值,並且如果方法以點開頭,它會被方法使用。$_ 可以顯式地透過名稱使用,也可以隱式地使用。

$_ = "Hello ";
.print;           # Call method 'print' on $_
$_.print;         # Same
print $_;         # Same, but written as a sub;

given "world!" {  # Different way of saying $_ = "world"
    .print;       # Same as print $_;
}

預設變數在許多地方都很有用,例如迴圈,它們可以用來清理程式碼並使操作更明確。我們將在後面的章節中更詳細地討論預設變數。

上下文

[編輯 | 編輯原始碼]

我們已經討論了各種基本資料型別:標量、陣列和雜湊。每個變數的符號將它置於特定的 **上下文** 中。不同型別的變數在不同上下文中使用時表現不同。至少有兩種基本型別的上下文,我們現在將討論其中的兩種:標量上下文和列表上下文。標量是所有帶 $ 符號的變數,而列表是像陣列和雜湊這樣的東西。

插曲:say

[編輯 | 編輯原始碼]

我們將藉此機會討論 Raku 的內建函式之一:saysay 在程式執行時將一行文字列印到控制檯。這是我們將在後面更詳細討論的更大的輸入/輸出(I/O)系統的一部分。say 接收一個字串或字串列表,並將它們列印到控制檯。

say "hello world!";
say "This is ", "Raku speaking!";

插曲:範圍

[編輯 | 編輯原始碼]

範圍是具有某種數值關係的值列表。範圍使用 .. 運算子建立。

my @digits = 0..9;        # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

範圍也可以使用變數作為分隔符。

my $max = 15;
my $min = 12;
my @list = $min .. $max;  # (12, 13, 14, 15);

範圍與陣列是完全不同的型別物件,即使範圍會建立類似陣列的值列表。範圍實現了一種稱為 **延遲求值** 的行為:範圍不會先計算所有值,而是以緊湊的方式儲存為起始點和結束點。只有當真正讀取範圍中的值時,才會從範圍中計算值。這意味著我們可以輕鬆地擁有無限範圍,而不會佔用我們計算機的所有記憶體。

my @range = 1..Inf;       # Infinite range, finite memory use
my $x = @range[1000000];  # Calculated on demand

延遲求值不僅是範圍的行為,它實際上是直接內置於 Raku 中,並在許多地方自動使用。這意味著大型陣列不一定佔用大量記憶體,而是可以僅在需要時計算值。

上下文指定符

[編輯 | 編輯原始碼]

我們可以使用各種強制轉換技術來指定資料項的上下文。$( ) 強制括號之間的任何內容都被視為標量,即使它通常不是標量。@( )%( ) 對陣列和雜湊也做同樣的事情。由於 ~ 始終與字串相關聯,而 + 始終與數字相關聯,因此我們可以使用它們將值分別強制轉換為字串和數字。

my Num $x = +"3";             # Becomes a number
my Str $y = ~3;               # The string "3"
my $z = $(2..4);              # Three elements, but still a single Range object
my @w = @("key" => "value");  # Convert hash to array

上面的例子並不是唯一可以將資料從一種型別轉換為另一種型別的例子。強制轉換允許我們將變數強制轉換為特定型別。在某些情況下,複雜的變數型別或類可能會根據其強制轉換方式表現出截然不同的行為。我們將在後面的章節中討論這些情況。

這裡是一個快速列出的上下文指定符。

+( )
轉換為數字
~( )
轉換為字串
$( )
轉換為標量
@( )
轉換為陣列
%( )
轉換為雜湊

我們之前討論過 Raku 是一種動態語言,因此其中的變數和資料項沒有預定義的型別。我們還提到了(幾乎是在腳註中)Raku 也有一個型別系統,如果需要,可以可選地使用它。Raku 是一種非常靈活的語言,它被設計為讓程式設計師能夠以多種不同的方式進行程式設計。Raku 為程式設計提供的一種方法是結構化的、靜態型別的程式設計。

如果您為變數指定了一個型別,Raku 會遵循該型別,並且只允許該型別的資料在該變數中使用。這在某些情況下非常有用,因為某些類別的操作會導致編譯時錯誤而不是執行時錯誤。此外,如果編譯器知道變數的型別永遠不會改變,它可以自由地執行某些型別的最佳化。這些行為是依賴於實現的,當然 - 只有當你嘗試利用它們時才會有效。一般規則是,你提供給編譯器的資訊越多,編譯器就能為你進行越有用的分析。

詞法變數

[編輯 | 編輯原始碼]

我們之前還提到過,變數不需要在使用之前顯式宣告。這也稍微有點偏離了事實。變數不需要預先宣告,但 Raku 讓你可以選擇在需要時進行宣告。要預先宣告變數,請使用 my 關鍵字。

my $x = 5;

my 定義變數為區域性詞法變數。另一種方法是將其定義為 our,使其成為全域性共享變數。Raku 中全域性變數的另一個名稱是“包變數”,因為它對整個軟體包或檔案可用。

our $x = 5;

全域性變數很好,因為你可以在任何地方使用它們,而無需傳遞它們並跟蹤它們。然而,與幼兒園不同,在大型程式中,共享並不總是最好的主意。

內建型別

[編輯 | 編輯原始碼]

Raku 提供了一些 Raku 編譯器事先知道的內建型別。你始終可以定義自己的型別,但 Raku 從一開始就提供了一些型別給你。這裡部分列出了一些 Raku 的基本內建型別。這不是一個完整的列表,因為 Raku 中的一些型別在目前階段無法理解。

布林值
布林值,真或假。布林值是列舉型別,我們將在稍後更深入地討論。布林值只能是 TrueFalse
整數
基本整數值。
陣列
由整型下標索引的值陣列。
雜湊
由字串索引的值雜湊。
數字
浮點數。
複數
類似浮點數,但也允許虛數和複數資料型別。
鍵值對
我們在討論雜湊時簡要地提到了鍵值對。鍵值對是一個數據物件和一個字串的組合。
字串
字串資料物件。

使用這些值,你可以像在普通靜態型別語言中一樣編寫靜態型別程式碼。

my Int $x;
$x = 5;         # Good, it's an integer
$x = "five";    # Bad, just the name of an integer

我們還可以使用型別系統來捕獲我們在變數之間移動資料時的錯誤。

my Int $foo = 5;
my Num $bar = 3.14;

$foo = $bar;      # ERROR! $bar is not an Int!

透過這種方式,編譯器可以告訴你,你是否試圖以你沒有預料到的方式使用變數。

運算子

[編輯 | 編輯原始碼]

在過去幾章中,我們一直在研究 Raku 資料以及儲存這些資料的變數。現在我們將探索獲得這些資料後可以對它們做些什麼。Perl 有一系列針對整數和浮點數的普通算術運算子,甚至還有一些針對其他資料型別的運算子。瞭解所有普通運算子後,我們就可以開始研究元運算子,它們將相同的概念應用於不同的上下文。

運算子在其 **運算元** 上工作,即進行運算的量。要理解任何運算子,你必須知道它的 **元數**(它需要多少個運算元)。如果它需要一個運算元,則稱為 **一元** 運算子;如果需要兩個運算元,則稱為 **二元** 運算子;如果需要三個運算元,則稱為 **三元** 運算子。

算術一元運算子

[編輯 | 編輯原始碼]

最簡單的算術運算子是一元符號運算子 +-。它們應用於數值的前部,以影響該數字的符號。

my Int $x = 5;
$y = -$x         # Now, $y is -5

還有一個字首 + 運算子,它不反轉符號。

算術二元運算子

[編輯 | 編輯原始碼]

與其他程式語言一樣,Raku 有許多算術運算子。

my $x = 12;
my $y = 3;
my $z;
$z = $x + $y;    # $z is 15
$z = $x - $y;    # $z is 9
$z = $y - $x;    # $z is -9
$z = $x * $y;    # $z is 36
$z = $x / $y;    # $z is 4
$z = $x % $y;    # $z is 0 (remainder)

算術運算子期望數值引數,因此引數將自動轉換為數字,就好像我們使用了上下文器 +( ) 一樣。

my Str $x = "123";
my Str $y = "456";
my Int $z = $x + $y;          # 579
my Int $w = +($x) + +($y);    # Same, but more verbose

字串和 ~

[編輯 | 編輯原始碼]

在 Raku 中,符號 ~ 始終與字串相關聯。當用作字首時,它會將它所作用的任何變數轉換為字串,即使它之前不是字串。當它用作兩個字串之間的運算子時,它將字串首尾相連,這個過程稱為 **連線**。

字串化

[編輯 | 編輯原始碼]

我們之前已經討論過使用 ~( ) 作為字串上下文說明符。這被稱為 **字串化**。字串化將變數從其他型別轉換為字串表示形式。

兩個字串可以連線在一起,生成一個新的字串。

my Str $x = "hello " ~ "world!";

~ 運算子會自動將任何不是字串的引數字串化。因此我們可以這樣寫

my Int $foo = 5;
my Str $bar = "I have " ~ $foo ~ " chapters to write";
print $bar;

這將打印出字串:I have 5 chapters to write

在大多數情況下,使用插值可能更容易,我們將在後面討論。

我們之前簡要地介紹了三種基本型別的字串:雙引號字串、單引號字串和 heredocs。單引號和雙引號字串看起來可能很相似,但它們的行為彼此不同。區別在於 **插值**。

雙引號字串是插值的。出現在字串內部的變數名將轉換為它們的字串值,幷包含在字串中。單引號字串沒有這種行為。

my Int $x = 5;
my Str $foo = "The value is $x";               # The value is 5
my Str $bar = 'The value is $x';               # The value is $x

遞增和遞減

[編輯 | 編輯原始碼]

變數增加或減少一個的操作非常常見,因此需要使用特定的運算子。++-- 運算子可以用作標量變數的字首或字尾。這兩個不同的位置有細微的差別。

my Int $x = 5;
$x++;            # 6 (increment is done after)
++$x;            # 7 (increment is done before)
$x--;            # 6 (as above) 
--$x;            # 5 (as above)

兩種形式,字首形式和字尾形式,在上面的示例程式碼中看起來通常執行相同的操作。程式碼 ++$x$x++ 都執行相同的操作,但操作發生的時間不同。讓我們用一些例子來說明這一點。

my Int $x = 5;
my Int $y;
$y = $x++;            # $y is 5, $x is 6
$y = ++$x;            # $y is 7, $x is 7
$y = $x--;            # $y is 7, $x is 6
$y = --$x;            # $y is 5, $x is 5

字首版本在變數在語句中使用之前執行遞增或遞減。字尾版本在變數使用後執行遞增或遞減。

下一頁: 控制結構 | 上一頁: 型別和上下文
主頁: Raku 程式設計

流程控制

[編輯 | 編輯原始碼]

我們在前面的章節中看到了如何建立變數以及如何使用它們執行基本的算術運算和其他操作。現在我們將介紹流程控制的概念,使用用於 **分支** 和 **迴圈** 的特殊結構。

塊是 { } 花括號內的程式碼塊。這些塊以多種方式與附近的其他程式碼區分開來。它們還為變數定義了新的 **範圍**:使用 my 定義並在塊內使用的變數在塊外部不可見或不可用。這使得程式碼可以進行分隔,以確保僅用於臨時用途的變數僅臨時使用。

分支可以使用以下兩個語句之一:**if** 和 **unless**。if 可以選擇性地包含一個 **else** 子句。if 語句評估給定的條件,如果它是真語句,則執行 if 之後的塊。當語句為假時,如果存在,則執行 else 塊。unless 語句執行相反的操作。它評估條件,只有在條件為假時才執行其塊。使用 unless 時,不能使用 else 子句。

關係運算符

[編輯 | 編輯原始碼]

有多種關係運算符可用於確定真值。以下是一些例子

$x == $y;                  # $x and $y are equal
$x > $y;                   # $x is greater than $y
$x >= $y;                  # $x is greater than or equal to $y
$x < $y;                   # $x is less than $y
$x <= $y;                  # $x is less than or equal to $y
$x != $y;                  # $x is not equal to $y

所有這些運算子都返回一個布林值,可以將其分配給一個變數。

$x = (5 > 3);              # $x is True
$y = (5 == 3);             # $y is False

上面的括號僅用於清晰起見;它們實際上並非必需。

if/unless

[編輯 | 編輯原始碼]

讓我們從一個例子開始

my Int $x = 5;
if ($x > 3) {
     say '$x is greater than 3';         # This prints
}
else {
     say '$x is not greater than 3';     # This doesn't
}

請注意,在上面的示例中,if($x > 3) 之間有一個空格。這一點很重要,不是可選的。Raku 的解析規則在這點上很明確:任何詞語後跟一個 ( 開括號將被視為子例程呼叫。空格將此語句與子例程呼叫區分開來,並讓解析器知道這是一個條件語句。

if($x > 5) {   # Calls subroutine "if"
}

if ($x > 5) {  # An if conditional
}

為了避免任何混淆,可以安全地省略括號

if $x > 5 {     # Always a condition
}

unless 的行為與 if 相反。

my Int $x = 5;
unless $x > 3 {
     say '$x is not greater than 3';         # This doesn't print
}

unless 之後不允許使用 else 子句。

字尾語法

[編輯 | 編輯原始碼]

ifunless 不僅可以用於標記要條件執行的塊。它們還可以以自然的方式應用於語句的末尾,以僅影響該語句。

$x = 5 if $y == 3;
$z++ unless $x + $y > 8;

上面的這兩行程式碼只有在滿足其條件的情況下才會執行。第一行在 $y 等於 3 的情況下將 $x 設定為 5。第二行在 $x + $y 的總和大於 8 的情況下遞增 $z

智慧匹配

[編輯 | 編輯原始碼]

有時您需要檢查兩件事是否匹配。關係運算符== 檢查兩個值是否相等,但這非常有限。如果我們想要檢查其他相等關係怎麼辦?我們想要的是一個無論這意味著什麼,都能做我們想做的事的運算子。這個神奇的運算子就是智慧匹配運算子~~

現在,當您看到~~運算子時,您可能立即會想到字串。智慧匹配運算子在字串中做了很多工作,但並不侷限於字串。

以下是一些智慧匹配運算子起作用的示例

5 ~~ "5";                          # true, same numerical value
["a", "b"] ~~ *, "a", *;           # true, "a" contained in the array
("a" => 1, "b" => 2) ~~ *, "b", *; # true, hash contains a "b" key
"c" ~~ /c/;                        # true, "c" matches the regex /c/
3 ~~ Int                           # true, 3 is an Int

如您所見,智慧匹配運算子可以用多種方式來測試兩件事,以檢視它們是否以某種方式匹配。在上面,我們看到了正則表示式的示例,我們將在後面的章節中更詳細地討論它。這也不是可以匹配事物的完整列表,我們將在整本書中看到更多內容。

給定 / 當

[編輯 | 編輯原始碼]

Raku 有一個將數量與多個不同備選項進行匹配的功能。這種結構是givenwhen 塊。

given $x {
  when Bool { say '$x is the boolean quantity ' ~ $x; }
  when Int { when 5 { say '$x is the number 5'; } }
  when "abc" { say '$x is the string "abc"'; }
}

每個when 都是一個智慧匹配。上面的程式碼等效於以下程式碼

if $x ~~ 5 {
  say '$x is the number 5';
} 
elsif $x ~~ "abc" {
  say '$x is the string "abc"';
}
elsif $x ~~ Bool {
  say '$x is the boolean quantity ' ~$x;
}

given/when 結構比if/else 更簡潔,並且在內部它可能以更最佳化的方式實現。

迴圈是多次重複某些語句組的方式。Raku 有許多可用的迴圈型別,每種型別都有不同的用途。

for 迴圈

[編輯 | 編輯原始碼]

for 塊接受陣列或範圍引數,並遍歷每個元素。在最基本的情況下,for 將每個連續的值分配給預設變數$_。或者,可以列出特定變數以接收值。以下是一些for 塊的示例

# Prints the numbers "12345"
for 1..5 {          # Assign each value to $_
    .print;           # print $_;
}

# Same thing, but using an array
my @nums = 1..5;
for @nums {
    .print;
}

# Same, but uses an array that's not a range
my @nums = (1, 2, 3, 4, 5);
for @nums {
    .print;
}

# Using a different variable than $_
for 1..5 -> $var {
    print $var;
}

在上面所有示例中,for 的陣列引數也可以可選地括在括號中。特殊的“尖角”語法-> 將在後面詳細解釋,雖然值得注意的是,我們可以將其擴充套件為在每次迴圈迭代中從陣列中讀取多個值

my @nums = 0..5;
for @nums -> $even, $odd {
  say "Even: $even Odd: $odd";
}

這將列印以下行

Even: 0 Odd: 1
Even: 2 Odd: 3
Even: 4 Odd: 5

for 也可以用作語句字尾,就像我們對ifunless 所做的那樣,儘管有一些警告

print $_ for (1..5);    # Prints "12345"
print for (1..5);        # Parse Error! Print requires an argument
.print for 1..5;  # Prints "12345"

C 程式設計師會認識到loop 結構的行為,它與 C 中的for 迴圈具有相同的格式和行為。Raku 已經為我們上一節中看到的陣列迴圈結構重新使用了名稱for,並使用名稱loop 來描述 C 迴圈的增量行為。以下是 loop 結構

loop (my $i = 0; $i <= 5; $i++) {
    print $i;            # "12345"
}

通常,loop 包含以下三個元件

loop ( INITIALIZER ; CONDITION ; INCREMENTER )

loop 中的INITIALIZER 是一行在迴圈開始之前執行的程式碼,但具有與迴圈主體相同的詞法範圍。CONDITION 是在每次迭代之前檢查的布林測試。如果測試為假,迴圈退出,如果為真,迴圈重複。INCREMENTER 是在每次迭代開始之前,迴圈結束時發生的一條語句。所有這些部分都可以選擇性地省略。以下五種方式可以寫出同一個迴圈

loop (my $i = 0; $i <= 5; $i++) {
    print $i;            # "12345"
}

my $i = 0;    # Small Difference: $i is scoped differently
loop ( ; $i <= 5; $i++) {
    print $i;
}

loop (my $i = 0; $i <= 5; ) {
    print $i;            # "12345"
    $i++;
}

loop (my $i = 0; ; $i++) {
    last unless ($i <= 5);
    print $i;            # "12345"
}

my $i = 0;
loop ( ; ; ) {
    last unless ($i <= 5);
    print $i;            # "12345"
    $i++;
}

如果要使用無限迴圈,也可以省略括號而不是使用 (;;)

my $i = 0;
loop {   # Possibly infinite loop
    last unless ($i <= 5);
    print $i;            # "12345"
    $i++;
}

repeat

[編輯 | 編輯原始碼]

重複塊將至少執行一次其主體,因為條件位於塊之後。在下面的示例中,您可以看到,即使$i 大於 2,塊仍將執行。

my $i = 3;
repeat {
    say $i;
} while $i < 2;

子程式

[編輯 | 編輯原始碼]

在程式碼重用方面,最基本的構建塊是子程式。然而,它們並不是工具箱中唯一的構建塊:Raku 還支援方法子方法,我們將在討論類和物件時討論它們。

子程式是使用sub 關鍵字建立的,後面跟著名稱、可選的引數列表,然後是一個程式碼塊。

塊是包含在{ } 大括號中的程式碼組。塊有很多用途,包括將程式碼隔開、將多個語句組合在一起以及為變數建立作用域。

定義子程式

[編輯 | 編輯原始碼]

子程式是使用sub 關鍵字定義的。以下是一個示例

sub mySubroutine () {
}

括號用於定義子程式的形式引數列表。引數就像普通的my 區域性變數,不同之處在於它們在子程式被呼叫時用值初始化。子程式可以使用return 關鍵字將結果傳回其呼叫者

sub double ($x) {
    my $y = $x * 2;
    return $y;
}

可選引數

[編輯 | 編輯原始碼]

可選引數在其後有?。此外,可選引數可以使用= 給出預設值。必需引數在其後可能有一個!,儘管這是位置引數的預設值。所有必需引數都必須列在所有可選引數之前。

sub foo (
    $first,       # First parameter, required
    $second!,     # Second parameter, required
    $third?,      # Third parameter, optional (defaults to undef)
    $fourth = 4   # Fourth parameter, optional (defaults to 4)
)

命名引數

[編輯 | 編輯原始碼]

正常引數按其位置傳遞:第一個傳遞的引數進入第一個位置引數,第二個進入第二個,依此類推。但是,也有一種方法可以按名稱傳遞引數,並且可以按任何順序傳遞。命名引數基本上是成對的,其中一個字串名稱與一個數據值相關聯。命名資料值可以使用成對或副詞語法傳遞。

sub mySub(:name($value), :othername($othervalue))

當然,子程式簽名允許使用特殊簡寫,如果您的變數與成對的名稱相同,您可以使用它

sub mySub(:name($name), :othername($othername))

sub mySub(:$name, :$othername)      # Same!

在子程式宣告中,命名引數必須放在所有必需和可選位置引數之後。命名引數預設情況下被視為可選,除非它們後面跟著!。實際上,您也可以在必需的位置引數之後放一個!,但這是預設值。

sub mySub(
    :$name!,             # Required
    :$type,              # Optional
    :$method?            # Still optional
)

貪婪引數

[編輯 | 編輯原始碼]

Raku 還允許使用*@ 語法進行所謂的“貪婪”引數。

sub mySub($scalar, @array, *@theRest) {
  say "the first argument was: $scalar";
  say "the second argument was: " ~ @array;
  say "the rest were: " ~ @theRest;
}

*@ 告訴 Raku 將剩餘的引數展平到列表中並存儲在陣列@theRest 中。這是允許 perl 接受位置或命名陣列而無需引用所必需的。

  my $first = "scalar";
  my @array = 1, 2, 3;
  mySub($first, @array, "foo", "bar");

上面的程式碼將輸出三行

  • 第一個引數是:標量
  • 第二個引數是:1, 2, 3
  • 其餘的是:"foo", "bar"

returnwant

[編輯 | 編輯原始碼]

呼叫子程式

[編輯 | 編輯原始碼]

一旦定義了子程式,我們就可以稍後呼叫它來獲取結果或操作。我們已經看到了內建的 say 函式,您可以向其傳遞字串,並讓這些字串列印到控制檯。我們可以使用上面的 double 函式來計算各種值。

my $x = double(2);       # 4
my $y = double(3);       # 6
my $z = double(3.5);     # 7

我們可以使用 & 符號將子程式的引用儲存到普通的標量變數中。

my $sub = &double;
my $x = $sub(7)          # 14

多個子程式

[編輯 | 編輯原始碼]

在這個例子中,您看到我們正在向 double 子程式傳遞整數和浮點數。但是,我們可以使用型別說明符來限制值的型別。

sub double (Int $x) {    # $x can only be an int!
   return $x * 2;
}

my $foo = double(4);     # 8
my $bar = double(1.5);   # Error!

Raku 允許您編寫多個具有相同名稱的函式,只要它們具有不同的引數簽名並且用關鍵字 multi 標記。這被稱為 **多方法分派**,是 Raku 程式設計的重要方面。

multi sub double(Int $x) {
    my $y = $x * 2;
    say "Doubling an Integer $x: $y";
    return $x * 2;
}

multi sub double(Num $x) {
    my $y = $x * 2;
    say "Doubling a Number $x: $y";
    return $x * 2;
}

my $foo = double(5);        # Doubling an Integer 5: 10
my $bar = double(3.5);      # Doubling a Number 3.5: 7

匿名子程式

[編輯 | 編輯原始碼]

我們可以定義一個 **匿名子程式** 並將對它的引用儲存在變數中,而不是像往常一樣命名子程式。

my $double = sub ($x) { return $x * 2; };
my $triple = sub ($x) { return $x * 3; };

my $foo = $double(5);     # 10
my $bar = $triple(12);    # 36

注意,我們也可以將這些程式碼引用儲存在陣列中。

my @times;
@times[2] = sub ($x) { return $x * 2; };
@times[3] = sub ($x) { return $x * 3; };

my $foo = @times[2](7);     # 14
my $bar = @times[3](5);     # 15

關於塊

[編輯 | 編輯原始碼]

當我們討論子程式時,我們發現子程式宣告由三個部分組成:子程式名稱、子程式引數列表以及子程式內部程式碼塊。塊在 Raku 中非常基礎,我們現在將使用它們來做各種很酷的事情。

我們已經看到一些塊在各種構造中使用。

# if/else statements
if $x == 1 {
}
else {
}

# subroutines
sub thisIsMySub () {
}

# loops
for @ary {
}

loop (my $i = 0; $i <= 5; $i++) {
}

repeat {
} while $x == 1;

所有這些塊都用於將程式碼行組合在一起以實現特定目的。在 if 塊中,當 if 條件為真時,塊內的所有語句都會執行。如果條件為假,則整個塊不會執行。在迴圈中,迴圈塊中的所有語句都會一起重複執行。

除了將類似的程式碼組合在一起之外,塊還引入了範圍的概念。在塊內定義的 my 變數在塊外不可見。範圍確保變數只在需要時使用,並且在不應該修改時不會被修改。塊不需要與任何特定構造相關聯,比如 ifloop。塊可以獨立存在。

my $x = 5;
my $y = 5;
{
   my $y = 3;
   say $x;         # 5
   say $y;         # 3
}
say $x;            # 5
say $y;            # 5

該示例很好地展示了範圍的概念:塊內的變數 $y 與塊外的變數 $y 不相同。即使它們具有相同的名稱,但它們具有不同的範圍。以下是一個稍微不同的示例。

my $x = 5;
{
   my $y = 7;
   {
      my $z = 9;
      say $x;  # 5
      say $y;  # 7
      say $z;  # 9
   }
   say $x;     # 5
   say $y;     # 7
   say $z;     # ERROR: Undeclared variable!
}
say $x;        # 5
say $y;        # ERROR! Undeclared variable!
say $z;        # ERROR! Undeclared variable!

變數 $x 從其定義點開始可見,並且在定義它的範圍內的所有範圍內部也可見。但是,$y 只能在定義它的塊和該塊內部的塊內可見。$z 只能在最內層的塊中可見。

範圍變數

[編輯 | 編輯原始碼]

在存在歧義的情況下,可以準確地指定範圍。我們可以使用 OUTER 等關鍵字來指定來自當前範圍直接上層範圍的變數。

my $x = 5;
{
   my $x = 6;
   say $x;           # 6
   say $OUTER::x    # 5
}

子程式可以使用 CALLER 範圍訪問它們被呼叫的範圍,假設外層範圍中的變數被宣告為 is context

my $x is context = 5;
mySubroutine(7);

sub mySubroutine($x) {
   say $x;         # 7
   say $CALLER::x; # 5
}

程式碼引用

[編輯 | 編輯原始碼]

塊可以作為程式碼引用儲存在單個標量變數中。一旦儲存在程式碼引用變數中,塊就可以像常規子程式引用一樣執行。

my $dostuff = {
   print "Hello ";
   say "world!";
}

$dostuff();

我們在上面的示例中看到,塊可以儲存在變數中。此操作建立一個 **閉包**。閉包是一個儲存的程式碼塊,它儲存其當前狀態和當前範圍,這些狀態和範圍可以在以後訪問。讓我們看看閉包的實際應用。

my $block;
{
    my $x = 2;
    $block = { say $x; };
}
$block();   # Prints "2", even though $x is not in scope anymore

閉包在閉包建立時儲存對 $x 變數的引用。即使該變數在程式碼塊執行時不再處於作用域內。

當我們稍後更改 $x 時,閉包將看到更改後的值,因此如果您想建立多個包含不同封閉變數的閉包,則每次都必須建立一個新變數。

my @times = ();
for 1..10 {
   my $t = $_;          # each subroutine gets a different $t
   @times[$_] = sub ($a) { return $a * $t; };
}

say @times[3](4);       # 12
say @times[5](20);      # 100
say @times[7](3);       # 21

尖角塊

[編輯 | 編輯原始碼]

我們可以使用 sub 關鍵字來建立子程式或子程式引用。這不是執行此操作的唯一語法,實際上對於未命名(“匿名”)子程式或子程式引用的常見情況來說,它有點冗長。對於這些,我們可以使用一種稱為 **尖角塊** 的構造。尖角塊(在其他語言中稱為 *lambda* 塊)非常有用。它們可以像匿名子程式一樣建立程式碼引用,並且還可以建立帶引數的程式碼塊。尖角塊很像未命名的子程式。更一般地說,它就像一個帶引數的塊。當我們討論迴圈時,我們簡要地看到了尖角塊。我們在迴圈構造關聯中使用尖角塊來為迴圈變數命名,而不是依賴預設變數 $_。這就是我們在這些情況下使用尖角塊的原因:它們使我們能夠指定變數名用作任意程式碼塊的引數。

我們將展示一些示例。

my @myArray = (1, 2, 3, 4, 5, 6);

# In a loop:
for @myArray -> $item {
    say $item;

# Output is:
#    1
#    2
#    3
#    4
#    5
#    6

}
# In a loop, multiples
for @myArray -> $a, $b, $c {
    say "$a, $b, $c";

# Output is:
#    1, 2, 3
#    4, 5, 6

}
# As a condition:
my $x = 5;
if ($x) -> $a { say $a; }  # 5
# As a coderef
my $x = -> $a, $b { say "First: $a.  Second: $b"; }
$x(1, 2);       # First: 1, Second: 2
$x("x", "y");   # First: x, Second: y
# As an inline coderef
-> $a, $b { say "First: $a, Second: $b"; }(1, 2)
#In a while loop
while ($x == 5) -> $a {
   say "Boolean Value: $a";
}

佔位符引數

[編輯 | 編輯原始碼]

在塊中,如果我們不想費力地寫出引數列表,我們可以使用佔位符引數。佔位符使用特殊的 ^ twigil。傳遞的值將按字母順序分配給佔位符變數。

for 1..3 {
  say $^a;    # 1
  say $^c;    # 3
  say $^b;    # 2
}

類和物件

[編輯 | 編輯原始碼]

到目前為止我們看到的都是面向過程程式設計的基礎:表示式列表、分支、迴圈以及告訴計算機做什麼以及如何準確地執行的子程式。Raku 非常支援面向過程程式設計,但這不是 Raku 支援的唯一程式設計風格。Raku 很好地適應的另一個常見正規化是面向物件方法。

物件是資料及其作用於該資料的操作的組合。物件的資料稱為其屬性,而物件的操作稱為其方法。從這個意義上說,屬性定義了狀態,方法定義了物件的行為。

類是建立物件的模板。當引用特定類的物件時,通常將該物件稱為該類的例項。

類使用 class 關鍵字定義,並被賦予一個名稱。

class MyClass {

}

在類宣告中,您可以定義屬性、方法或子方法。

屬性使用 has 關鍵字定義,並使用特殊的語法指定。例如,讓我們考慮以下類

class Point3D {
    has $!x-axis;
    has $!y-axis;
    has $!z-axis;
}

類 Point2D 定義了一個三維座標系中的點,它有三個名為 x 軸、y 軸和 z 軸的屬性。

在 Raku 中,所有屬性都是私有的,使用 ! 識別符號可以明確地表達這一點。使用 ! 識別符號宣告的屬性只能在類中使用 !attribute-name 直接訪問。這種方式宣告屬性的另一個重要後果是,物件不能使用預設的 new 建構函式進行填充。

如果使用 . 識別符號宣告屬性,則會自動生成一個只讀訪問器[檢查拼寫] 方法。您可以將 . 識別符號理解為 "屬性 + 訪問器"。這個訪問器是一個以其屬性命名的方法,可以從類外部呼叫並返回其屬性的值。為了允許透過提供的訪問器對屬性進行更改,必須在屬性中新增 is rw 特性。

之前的 Point3D 類可以宣告如下

class Point3D {
    has $.x-axis;
    has $.y-axis;
    has $.z-axis is rw;
}

鑑於 . 識別符號聲明瞭一個 ! 識別符號和一個訪問器[檢查拼寫] 方法,即使使用 . 識別符號宣告屬性,屬性也可以始終使用 ! 識別符號。

方法的定義與普通子程式類似,但有一些關鍵區別

  1. 方法使用 method 關鍵字代替 sub
  2. 方法有特殊的變數 self,它指的是正在呼叫方法的物件。這被稱為**呼叫者**。
  3. 方法可以直接訪問物件的內部特徵。

在定義方法時,您可以為呼叫者指定不同的名稱,而不是必須使用 self。為此,您將它放在方法簽名的開頭,並用冒號與簽名中的其他部分隔開

method myMethod($invocant: $x, $y)

在這種情況下,冒號被視為一種特殊的逗號,因此您可以用額外的空格來寫它,如果這樣更容易

method myMethod($invocant  :  $x, $y)

這是一個例子

class Point3D {
    has $.x-axis;
    has $.y-axis;
    has $.z-axis;
    
    method set-coord($new-x, $new-y, $new-z) {
        $!x-axis = $new-x;
        $!y-axis = $new-y;
        $!z-axis = $new-z;
    }
    
    method print-point {
        say "("~$!x-axis~","~$!y-axis~","~$!z-axis~")";
    }
    
    # method using the self invocant
    method distance-to-center {
        return sqrt(self.x-axis ** 2 + self.y-axis ** 2);
    }
    
    # method using a custom invocant named $rect
    method polar-coordinates($rect:) {
        my $r = $rect.distance-to-center;
        my $theta = atan2($rect.y-axis, $rect.x-axis);
        return "("~$r~","~$theta~","~$rect.z-axis~")";
    }
}

物件是資料項,其型別為給定的類。物件包含類定義的任何屬性,並且還可以訪問類中的任何方法。物件使用 new 關鍵字建立。

使用 Point3D 類

my $point01 = Point3D.new();

**類建構函式** new() 可以使用命名方法來初始化類的任何屬性

# Either syntax would work for object initialization
my $point01 = Point3D.new(:x-axis(3), :y-axis(4), :z-axis(6));
my $point02 = Point3D.new(x-axis => 3, y-axis => 4, z-axis => 6);

該類中的方法使用點表示法呼叫。這是物件、句點以及該方法之後的名稱。

$point01.print-point();
say $point01.polar-coordinates();

當點表示法方法呼叫沒有提供物件時,預設變數 $_ 會被使用

$_ = Point3D.new(:x-axis(6), :y-axis(8), :z-axis(6));;
.print-point();
say .polar-coordinates();

基本類系統使資料和操作這些資料的程式碼例程能夠以邏輯方式捆綁在一起。但是,大多數類系統的更高階功能也支援繼承,這允許類相互構建。**繼承**是類形成邏輯層次結構的能力。Raku 支援類和子類的正常繼承,但也支援稱為**mixin**和**role**的特殊高階功能。我們必須將其中一些更復雜的功能留到後面的章節中,但這裡將介紹一些基礎知識。

基本型別

[編輯 | 編輯原始碼]

我們在前面的章節中討論了 Perl 的一些基本型別。您可能會驚訝地發現,所有 Raku 資料型別都是類,並且所有這些值都具有內建的方法可以使用。在這裡,我們將討論一些可以呼叫我們在迄今為止看到的各種物件的方法。

.print.say

[編輯 | 編輯原始碼]

我們已經看到了 printsay 內建函式。所有內建類都有同名方法,這些方法列印物件的字串化形式。

.perleval

[編輯 | 編輯原始碼]

我們將快速離題,討論 eval 函式。eval 允許我們在執行時編譯和執行 Raku 程式碼字串。

eval("say 'hello world!';");

所有 Raku 物件都有一個名為 .perl 的方法,該方法返回一個 Raku 程式碼字串,代表該物件。

my Int $x = 5;
$x.perl.say;          # "5"

my @y = (1, 2, 3);
@y.perl.say;          # "[1, 2, 3]"

my %z = :first(1), :second(2), :third(3);
%z.perl.say;          # "{:first(1), :second(2), :third(3)}"

上下文和強制轉換方法

[編輯 | 編輯原始碼]

有一些方法可以被呼叫來顯式地將給定的資料項更改為不同的形式。這就像一種顯式的方式,強制給定的資料項在不同的上下文中被使用。以下是一個部分列表

.item
在標量上下文中返回專案。
.iterator
返回物件的迭代器。我們將在後面的章節中討論迭代器。
.hash
在雜湊上下文中返回物件
.list
在陣列或 "列表" 上下文中返回物件
.Bool
返回物件的布林值
.Array
返回包含物件資料的陣列
.Hash
返回包含物件資料的雜湊表
.Iterator
返回物件的迭代器。我們將在後面的章節中討論迭代器。
.Scalar
返回對物件的標量引用
.Str
返回物件的字串表示

內省方法

[編輯 | 編輯原始碼]
.WHENCE
返回物件型別的自動生存閉包的程式碼引用。我們將在後面討論自動生存和閉包。
.WHERE
返回資料物件的記憶體位置地址
.WHICH
返回物件的標識值,對於大多數物件來說,它只是它的記憶體位置(它的 .WHERE)
.HOW
(HOW = Higher Order Workings) 返回處理此物件的元類
.WHAT
返回當前物件的型別物件
下一頁: 註釋和 POD | 上一頁: 塊和閉包
主頁: Raku 程式設計

現在我們已經涵蓋了 Raku 程式設計的大部分基礎知識。當然,我們並沒有涵蓋整個語言。但是,我們已經看到了用於普通程式設計任務的基本工具型別。還有很多東西需要學習,許多高階工具和功能可以用來使常見任務更容易,而困難的任務則變得可能。我們將在稍後介紹一些更高階的功能,但在本章中,我們想透過談論一些關於註釋和文件的內容來結束“基礎”部分。

我們之前提到過,註釋是原始碼中的註釋,旨在供程式設計師閱讀,並且被 Raku 直譯器忽略。Raku 中最常見的註釋形式是單行註釋,它以單個井號 # 開頭,一直延伸到行尾。

# Calculate factorial of a number using recursion
sub factorial (Int $n) {
    return 1 if $n == 0;            # This is the base case
    return $n * factorial($n - 1);  # This is the recursive call
}

當以上程式碼執行時,所有以單個井號 # 為字首的文字將被 Raku 直譯器忽略。

多行註釋

[edit | edit source]

雖然 Perl 不提供多行註釋,但 Raku 提供。為了在 Raku 中建立多行註釋,註釋必須以單個井號、反引號、然後是一些開括號字元開頭,並以匹配的閉括號字元結尾。

sub factorial(Int $n) {
    #`( This function returns the factorial of a given parameter
      which must be an integer. This is an example of a recursive
      function where there is a base case to be reached through
      recursive calls.
      )
    
    return 1 if $n == 0;            # This is the base case
    return $n * factorial($n - 1);  # This is the recursive call
}

此外,註釋的內容也可以嵌入到行內。

sub add(Int $a, Int $b) #`( two (integer) arguments must be passed! ) {
    return $a + $b;
}

POD 文件

[edit | edit source]

規則和語法

[edit | edit source]

正則表示式

[edit | edit source]

正則表示式是一種用於指定和搜尋字串中的模式的工具,除此之外還有其他用途。正則表示式是 Perl 中一個流行且強大的部分,儘管它們在該語言的後續版本中逐漸增長和擴充套件,以一種難以跟蹤和實現的方式。

隨著更多運算子和元字元被新增到引擎中,Perl 的正則表示式變得越來越難以使用和理解。因此,人們決定 Raku 將打破這種語法,從頭開始重寫正則表示式,使其更加靈活,並更好地整合到語言中。在 Raku 中,它們被稱為regexes,並且變得更加強大。

Raku 以兩種方式支援 regexes:它有一個支援 Perl 風格正則表示式的遺留模式,以及一個支援新風格正則表示式的正常模式。

基本量詞

[edit | edit source]

Regexes 描述了可以搜尋和操作的字串資料中的模式。要搜尋的最基本模式之一是重複模式。為了描述重複,可以使用一些量詞。

運算子 含義 示例 說明
* "零個或多個" B A* 接受一個以 'B' 開頭,後面跟隨任意數量的 'A' 字元的字串,即使是零個 'A' 字元。BBAAAAA 等。
+ "一個或多個" B A+ 接受一個以 'B' 開頭,後面跟隨至少一個 'A' 的字串。例如:BAAABA,但不包括 B
? "一個或零個" B A? 匹配一個 'B',可選地跟隨一個 'A'。BBA
** "這麼多" B A**5 匹配一個以 'B' 開頭,後面跟隨恰好 5 個 'A' 字元的字串。BAAAAA
B A ** 2..5 匹配一個以 'B' 開頭,後面跟隨至少兩個 'A',但不超過 5 個 'A' 的字串。BAABAAABAAAABAAAAA

語法

[edit | edit source]

正則表示式本身很有用,但有限。重用正則表示式可能很困難,將它們分組為邏輯分組可能很困難,從一個分組繼承到另一個分組可能非常困難。這就是語法的作用所在。語法對於正則表示式而言就像類對於資料和程式碼例程一樣。語法允許正則表示式像程式語言中正常的首要元件一樣工作,並利用類系統的強大功能。語法可以像類一樣被繼承和過載。實際上,Raku 語法本身可以被修改,以便在執行時為語言新增新功能。我們將在後面看到這些例子。

規則、標記和原型

[edit | edit source]

語法被分解成稱為規則標記原型的元件。標記就像我們已經見過的正則表示式。規則就像子程式,因為它們可以呼叫其他規則或標記。原型就像預設的多子程式,它們定義了一個可以被重寫的規則原型。

標記

[edit | edit source]

標記是不回溯的正則表示式,這意味著如果表示式的一部分已經被匹配,即使這會阻止表示式更大的一部分匹配,這一部分也不會被改變。雖然這犧牲了正則表示式的部分靈活性,但它允許更高效地建立更復雜的解析器。

token number {
  \d+ ['.' \d+]?
}

規則

[edit | edit source]

規則是將標記和其他規則組合在一起的方法。規則都帶有名稱,並且可以使用< > 尖括號引用同一個語法中的其他規則或標記。像標記一樣,它們不回溯,但它們內部的空格被逐字解釋,而不是被忽略。

rule URL {
  <protocol>'://'<address>
}

此規則匹配一個 URL 字串,其中協議名稱(例如 "ftp" 或 "https")後面跟著字面符號 "://",然後是一個表示地址的字串。此規則依賴於兩個子規則,<protocol><address>。這些可以定義為標記或規則,只要它們在同一個語法中即可。

grammar URL {
  rule TOP {
    <protocol>'://'<address>
  }
  token protocol {
    'http'|'https'|'ftp'|'file'
  }
  rule address {
    <subdomain>'.'<domain>'.'<tld>
  }
  ...
}

原型

[edit | edit source]

原型定義了規則或標記的型別。例如,我們可以定義一個原型標記<protocol>,然後定義幾個表示不同協議的標記。在一個標記內部,我們可以將其名稱引用為<sym>

grammar URL {
  rule TOP {
    <protocol>'://'<address>
  }
  proto token protocol {*}

  token protocol:sym<http> {
    <sym>
  }
  token protocol:sym<https> {
    <sym>
  }
  token protocol:sym<ftp> {
    <sym>
  }
  token protocol:sym<ftps> {
    <sym>
  }
  ...
}

這相當於說

token protocol {
  <http> | <https> | <ftp> | <ftps>
}
token http {
  http
}
...

但它更具可擴充套件性,允許以後指定協議型別。例如,如果我們想要定義一個新的URL 型別,它也支援 "spdy" 協議,我們可以使用

grammar URL::WithSPDY is URL {
  token protocol:sym<spdy> {
    <sym>
  }
}

匹配語法

[edit | edit source]

一旦我們有了像上面定義的語法,我們就可以使用.parse 方法來匹配它。

my Str $mystring = "http://www.wikibooks.org";

if URL.parse($mystring) {
  #if it matches a URL, do something
}

匹配物件

[edit | edit source]

匹配物件是一種特殊的資料型別,它表示語法的解析狀態。當前匹配物件儲存在特殊變數$/中。

解析器操作

[edit | edit source]

透過將語法與解析器操作類組合,可以將語法變成互動式解析器。當語法匹配某些規則時,可以呼叫相應的操作方法,並傳入當前匹配物件。

運算子只有5種類型:中綴字首字尾環繞後環繞

您可以像這樣宣告一個新的運算子

sub postfix:<!>(Int $n!) { [*] 1..$n }
say 5!; # prints 120

如您所見,以上程式碼聲明瞭一個運算子 '!' 用於計算整數的階乘。

資料型別和運算子

[編輯 | 編輯原始碼]

聯結最初是作為一個高階的 Perl 模組的一部分實現的,以簡化一些常見的操作。假設我們有一個複雜的條件,我們需要將變數 $x 與多個離散值中的一個進行比較

if ($x == 2 || $x == 4 || $x == 5 || $x == "hello" 
 || $x == 42 || $x == 3.14)

這非常混亂。我們想要做的是建立一個值的列表,並詢問“如果 $x 是這些值中的一個”。聯結允許這種行為,但也能做更多的事情。以下是使用聯結編寫的相同語句

if ($x == (2|4|5|"hello"|42|3.14))

聯結型別

[編輯 | 編輯原始碼]

聯結有 4 種基本型別:any(所有元件的邏輯 OR)、all(所有元件的邏輯 AND)、one(所有元件的邏輯 XOR)和 none(所有元件的邏輯 NOR)。

列表運算子

[編輯 | 編輯原始碼]

列表運算子將聯結構建為一個列表

my $options = any(1, 2, 3, 4);        # Any of these is good
my $requirements = all(5, 6, 7, 8);   # All or nothing
my $forbidden = none(9, 10, 11);      # None of these
my $onlyone = one(12, 13, 4);         # One and only one

中綴運算子

[編輯 | 編輯原始碼]

另一種指定聯結的方法是使用中綴運算子,就像我們已經看到的那樣

my $options = 1 | 2 | 3 | 4;        # Any of these is good
my $requirements = 5 & 6 & 7 & 8;   # All or nothing
my $onlyone = 12 ^ 13 ^ 4;          # One and only one

請注意,沒有中綴運算子來建立 none() 聯結。

聯結操作

[編輯 | 編輯原始碼]

匹配聯結

[編輯 | 編輯原始碼]

聯結與 Raku 中的任何其他資料型別一樣,可以使用智慧匹配運算子 ~~ 進行匹配。該運算子將根據要匹配的聯結型別自動執行正確的匹配演算法。

my $junction = any(1, 2, 3, 4);
if $x ~~ $junction {
    # execute block if $x is 1, 2, 3, or 4
}

all() 聯結

[編輯 | 編輯原始碼]
if 1 ~~ all(1, "1", 1.0)     # Success, all of them are equivalent
if 2 ~~ all(2.0, "2", "foo") # Failure, the last one doesn't match

只有當 all() 聯結中的所有元素都與物件 $x 匹配時,它才會匹配。如果任何元素不匹配,整個匹配將失敗。

one() 聯結

[編輯 | 編輯原始碼]

只有當 one() 聯結中恰好只有一個元素匹配時,它才會匹配。多一個或少一個,整個匹配都將失敗。

if 1 ~~ one(1.0, 5.7, "garbanzo!") # Success, only one match
if 1 ~~ one(1.0, 5.7, Int)         # Failure, two elements match

any() 聯結

[編輯 | 編輯原始碼]

只要至少有一個元素匹配,any() 聯結就會匹配。可以是一個或其他任何數量,但不能為零。any 聯結失敗的唯一方法是所有元素都不匹配。

if "foo" ~~ any(String, 5, 2.18)  # Success, "foo" is a String
if "foo" ~~ any(2, Number, "bar") # Failure, none of these match

none() 聯結

[編輯 | 編輯原始碼]

只有當聯結中沒有任何元素匹配時,none() 聯結才會成功匹配。這樣,它等同於 any() 聯結的逆。如果 any() 成功,none() 失敗。如果 any() 失敗,none() 成功。

if $x ~~ none(1, "foo", 2.18)
if $x !~ any(1, "foo", 2.18)    # Same thing!

在大多數傳統的計算系統中,資料物件被分配到一個固定的大小,並將它們的值填充到記憶體中的空間中。例如,在 C 中,如果我們宣告一個數組 int a[10],則陣列 a 將是一個固定大小的陣列,有足夠的空間來儲存恰好 10 個整數。如果我們要儲存 100 個整數,我們需要分配一個可以儲存 100 個整數的空間。如果我們要儲存一百萬個整數,我們需要分配一個大小為一百萬的陣列。

讓我們考慮一個問題,我們要計算一個乘法表,一個二維陣列,其中陣列中給定單元格的值是它兩個索引的乘積。這是一個簡單的迴圈,可以使用它來生成因子不超過 N 的表格

int products[N][N];
int i, j;
for(i = 0; i < N; i++) {
    for(j = 0; j < N; j++) {
        products[i][j] = i * j;
    }
}

建立此表格可能需要一段時間才能執行所有 N2 操作。當然,一旦我們初始化了表格,在其中查詢值的速度非常快。這裡要考慮的另一件事是,我們最終計算的值比我們實際使用的值還要多,因此這是浪費的努力。

現在,讓我們看看執行相同操作的函式

int product(int i, int j) {
    return i * j;
}

此函式不需要任何啟動時間來初始化它的值,但是每次呼叫都需要額外的時間來計算結果。它比陣列啟動速度更快,但每次訪問所花費的時間比陣列多。

結合這兩個想法,我們就得到了延遲列表。

延遲列表

[編輯 | 編輯原始碼]

延遲列表就像陣列,但也有一些主要區別

  1. 它們並不一定用預定義的大小宣告。它們可以是任何大小,甚至可以是無限長的。
  2. 它們在需要之前不會計算它們的值,並且只在需要時計算需要的值。
  3. 一旦它們的值被計算出來,就可以儲存起來以進行快速查詢。

延遲列表的相反是急切列表。急切列表立即計算並存儲所有值,就像 C 中的陣列一樣。急切列表不能無限長,因為它們需要將它們的值儲存在記憶體中,而計算機沒有無限的記憶體。

Raku 同時擁有這兩種型別的列表,它們在內部處理,無需程式設計師干預。可以延遲的列表將被延遲處理。不能延遲的列表將被急切地計算並存儲。延遲列表在儲存空間和計算開銷方面提供了優勢,因此 Raku 嘗試預設使用它們。Raku 還提供了一些可以用來支援延遲並提高列表計算效能的構造。

我們已經看到了範圍。範圍預設情況下是延遲的,這意味著範圍中的所有值並不一定在您將它們分配給陣列時計算

my @lazylist = 1..20000;   # Doesn't calculate all 20,000 values

由於它們的延遲性,範圍甚至可以是無限的

my @lazylist = 1..Inf;     # Infinite values!

迭代器

[編輯 | 編輯原始碼]

迭代器是特殊的資料項,它們一次遍歷複雜資料物件中的一個元素。想象一下文字編輯器程式中的游標;游標讀取一次按鍵,將字元插入到它當前的位置,然後移動到下一個位置等待下一個按鍵。這樣,一個長字元陣列可以一次插入一個字元,而您(編輯器)無需手動移動游標。

同樣,Raku 中的迭代器自動遍歷陣列和雜湊,自動跟蹤您在陣列中的當前位置,因此您無需手動跟蹤。我們之前在迴圈討論中已經看到了迭代器的使用,儘管我們沒有將它們稱為“迭代器”。以下是兩個執行相同功能的迴圈

my @x = 1, 2, 3, 4, 5;
loop(my int $i = 0; $i < @x.elems; $i++) {
    @x[$i].say;
}

for @x {            # Same, but much shorter!
    $_.say;
}

第一個迴圈使用 $i 變數手動遍歷 @x 陣列,以跟蹤當前位置,並使用 $i < @x.length 測試來確保我們沒有到達末尾。在第二個迴圈中,for 關鍵字為我們建立了一個迭代器。迭代器會自動跟蹤我們當前在陣列中的位置,自動檢測我們何時到達陣列末尾,並自動將每個後續值載入到 $_ 預設變數中。值得一提的是,我們可以使用一些 Raku 習語使它更短

 .say for @x;

迭代器是什麼?

[編輯 | 編輯原始碼]

迭代器是任何實現了 `Iterator` 角色的物件。我們將在稍後討論 **角色**,但現在只需要知道角色是其他類可以參與的標準介面。由於它們可以是任何類,只要它具有標準介面,迭代器就可以執行我們定義的任何操作。迭代器可以輕鬆遍歷陣列和雜湊表,但專門定義的型別也可以遍歷樹、圖、堆、檔案以及各種其他資料結構和概念。

如果資料項具有關聯的迭代器型別,則可以透過 `Iterator()` 方法訪問它。此方法在大多數情況下由 `for` 迴圈等結構在內部呼叫,但如果你確實需要,你可以訪問它。

資料流

[edit | edit source]

資料流提供了一種直觀的圖形化方式來展示資料在複雜賦值語句中的流動。資料流有兩個端點,一個“鈍端”和一個“尖端”。鈍端連線到資料來源,資料來源是值列表。尖端連線到接收器,接收器可以一次接收至少一個元素。資料流可以用來將資料從右到左或從左到右傳送,具體取決於資料流指向的方向。

my @x <== 1..5;
say @x    # 1, 2, 3, 4, 5

@x ==> @y ==> print   # 1, 2, 3, 4, 5
say @y    # 1, 2, 3, 4, 5

分層資料流將資料從一個數據流移動到另一個數據流。但是,有兩個端點的資料流會附加到資料流鏈中的最後一個專案。

my @x = 1..5;
@x ==> map {$_ * 2} ==> @y;
say @x; # 1, 2, 3, 4, 5
say @y; # 2, 4, 6, 8, 10

@x ==>>
@y ==> @z;
say @z # 1, 2, 3, 4, 5, 2, 4, 6, 8, 10

收集和獲取

[edit | edit source]

我們可以使用 `gather` 和 `take` 關鍵字編寫我們自己的迭代器型別。這兩個關鍵字的行為非常類似於我們之前見過的尖角塊。但是,與尖角塊不同,`gather` 和 `take` 可以返回值。與尖角塊類似,`gather` 和 `take` 可以與迴圈結合使用,形成自定義迭代器。

`gather` 用於定義一個特殊的塊。該塊的程式碼可以執行任意計算,並使用 `take` 返回一個值。以下是一個示例

my $x = gather {
  take 5;
}
say $x;     # 5

這本身並不太有用。但是,我們現在可以將其與迴圈結合使用,以返回一個長值列表。

my @x = gather for 1..5 {
  take $_ * 2;
}

say @x     # 2, 4, 6, 8, 10

`take` 運算子執行兩個操作:它捕獲傳遞給它的值,並將該值作為 `gather` 塊的結果之一返回;它還返回傳遞給它的值以供儲存。我們可以很容易地將這種行為與 `state` 變數結合使用,以遞迴地使用值。

my @x = gather for 1..5 {
  state $a = $_;
  $a = take $_ + $a;
}
say @x;    # 2, 4, 7, 11, 16
下一頁: 元運算子 | 上一頁: 連線
主頁: Raku 程式設計

元運算子

[edit | edit source]

運算子對資料執行操作。元運算子對運算子執行操作。

列表運算子

[edit | edit source]

歸約運算子

[edit | edit source]

歸約運算子作用於列表,並返回一個標量值。它們透過在陣列中的每對元素之間應用歸約運算子來實現這一點。

my @nums = 1..5;
my $sum = [+] @nums     # 1 + 2 + 3 + 4 + 5

`[]` 方括號將任何通常作用於標量的運算子轉換為歸約運算子,以對列表執行相同的操作。歸約也可以與關係運算符一起使用。

my $x = [<] @y;   # true if all elements of @y are in ascending order
my $z = [>] @y;   # true if all elements of @y are in descending order

超運算子

[edit | edit source]

歸約運算子將運算子應用於陣列中的所有元素,並將陣列縮減為單個標量值。超運算子將操作分佈到列表中的所有元素,並返回所有結果的列表。超運算子使用特殊的“法語引號”符號構造:« 和 ». 如果你鍵盤不支援這些符號,可以使用 ASCII 符號 `>>` 和 `<<` 代替。

my @a = 1..5;
my @b = 6..10;
my @c = @a »*« @b;
# @c = 1*6, 2*7, 3*8, 4*9, 5*10

你也可以將一元運算子與超運算子一起使用。

my @a = (2, 4, 6);
my @b = -« @a;  # (-2, -4, -6)

一元超運算子始終返回一個與它接收的列表大小完全相同的陣列。二元超運算子的行為不同,具體取決於其運算元的大小。

@a »+« @b;   # @a and @b MUST be the same size
@a «+« @b;   # @a can be smaller, will upgrade
@a »+» @b;   # @b can be smaller, will upgrade
@a «+» @b;   # Either can be smaller, Perl will Do What You Mean

將超運算子符號指向不同的方向會影響 Raku 對元素的處理。在尖端,它會擴充套件陣列,使其與鈍端上的陣列一樣長。如果兩端都尖銳,它會擴充套件較小的一個。

超運算子也可以與賦值運算子一起使用。

@x »+=« @y  # Same as @x = @x »+« @y

交叉運算子

[edit | edit source]

交叉是“X”大寫字母符號。作為運算子,交叉返回透過組合其運算元的元素而形成的所有可能列表的列表。

my @a = 1, 2;
my @b = 3, 4;
my @c = @a X @b;  # (1,3), (1,4), (2,3), (2,4)

交叉也可以用作元運算子,對每個運算元的每個可能元素組合應用它修改的運算子。

my @a = 1, 2;
my @b = 3, 4;
my @c = @a X+ @b; # 1+3, 1+4, 2+3, 2+4

繼承

[edit | edit source]

基本類系統使資料和對該資料進行操作的程式碼例程能夠以邏輯方式捆綁在一起。但是,大多數類系統還具有更高階的功能,這些功能也支援繼承,這使類可以相互構建。 **繼承** 是類形成邏輯層次結構的能力。Raku 支援類的正常繼承和子類,但也支援稱為 **mixin** 和 **角色** 的特殊高階功能。我們必須將這些功能中比較困難的部分保留到後面的章節中討論,但我們會在本章中介紹它們。

類繼承

[edit | edit source]

角色和 `does`

[edit | edit source]

mixin

[edit | edit source]

引數化角色

[edit | edit source]

塊和子例程

[edit | edit source]

高階子例程

[edit | edit source]

我們之前討論過子例程和程式碼引用,但這些問題還有很多內容需要學習,而我們在那章中沒有足夠的空間進行討論。現在我們將介紹子例程和程式碼引用的一些更高階的功能。

`Code` 物件

[edit | edit source]

塊作為引數

[編輯 | 編輯原始碼]

柯里化

[編輯 | 編輯原始碼]

簽名物件

[編輯 | 編輯原始碼]

從最基本的意義上來說,異常代表著由程式導致的錯誤。但是,與讓程式崩潰不同,異常有機會被捕獲並優雅地處理。異常被稱為被**丟擲**或**引發**,特殊的程式碼塊稱為**處理程式**可以**捕獲**它們。

異常物件

[編輯 | 編輯原始碼]

處理程式和`CATCH`塊

[編輯 | 編輯原始碼]

屬性塊

[編輯 | 編輯原始碼]

我們在上一章中看到了特殊的`CATCH`塊,它用於處理從`CATCH`所在的塊中丟擲的異常。除了`CATCH`,還有許多其他特殊的**屬性塊**,可用於修改它們所在的塊的行為。

屬性塊本質上是詞法性的:它們修改了定義它們的塊的行為,並且不影響外部作用域。

NEXT和LAST塊

[編輯 | 編輯原始碼]

PRE和POST塊

[編輯 | 編輯原始碼]

KEEP和UNDO塊

[編輯 | 編輯原始碼]

執行順序

[編輯 | 編輯原始碼]

多工和併發

[編輯 | 編輯原始碼]

輸入和輸出

[編輯 | 編輯原始碼]

在開始之前

[編輯 | 編輯原始碼]

檔案控制代碼

[編輯 | 編輯原始碼]

在 Raku 中,任何與檔案的互動都是透過檔案控制代碼進行的。[註釋 1] 檔案控制代碼是外部檔案的內部名稱。`open`函式在內部名稱和外部名稱之間建立關聯,而`close`函式則斷開這種關聯。一些 IO 控制代碼可以在無需建立的情況下使用:`$*OUT`和`$*IN`分別連線到 STDOUT 和 STDIN,即標準輸出和標準輸入流。您需要自己開啟所有其他檔案控制代碼。

始終記住,程式中的任何檔案路徑都是相對於當前工作目錄的。

檔案操作:文字

[編輯 | 編輯原始碼]

以只讀方式開啟檔案

[編輯 | 編輯原始碼]

要開啟一個檔案,我們需要建立一個檔案控制代碼。這僅僅意味著我們建立一個(標量)變數,它將從現在開始引用該檔案。雙引數語法是呼叫`open`函式的最常見方式:`open PATHNAME, MODE`——其中`PATHNAME`是要開啟檔案的外部名稱,而`MODE`是訪問型別。如果成功,這將返回一個 IO 控制代碼物件,我們可以將其放入標量容器中

my $filename = "path/to/data.txt";
my $fh = open $filename, :r;

`:r`以只讀模式開啟檔案。為了簡短,您可以省略`:r`——因為它就是預設模式;當然,`PATHNAME`字串可以直接傳遞,而不是透過`$filename`變數傳遞。

一旦我們有了檔案控制代碼,就可以讀取檔案並對檔案執行其他操作。

新方法

[編輯 | 編輯原始碼]

請使用 `slurp` 和 `spurt` 代替,因為

"file.txt".IO.spurt: "file contents here";
"file.txt".IO.slurp.say; # «file contents here»

讀取已開啟的檔案

[編輯 | 編輯原始碼]

讀取檔案最通用的方法是使用 `open` 函式建立與資源的連線,然後執行資料讀取步驟,最後在開啟過程中接收的檔案控制代碼上呼叫 `close` 函式。

my $fileName;
my $fileHandle;

$fileName   = "path/to/data.txt";
$fileHandle = open $fileName, :r;

# Read the file contents by the desiderated means.

$fileHandle.close;

要將檔案資料立即完全傳輸到程式中,可以使用 `slurp` 函式。通常,這涉及將獲得的字串儲存到變數中,以便進行進一步操作。

my $fileName;
my $fileHandle;
my $fileContents;

$fileName   = "path/to/data.txt";
$fileHandle = open $fileName, :r;

# Read the complete file contents into a string.
$fileContents = $fileHandle.slurp;

$fileHandle.close;

如果由於行導向的程式設計任務或記憶體考慮,不希望完全讀取資料,可以透過 `IO.lines` 函式逐行讀取檔案。

my $fileName;
my $fileHandle;

$fileName   = "path/to/data.txt";
$fileHandle = open $fileName, :r;

# Iterate the file line by line, each line stored in "$currentLine".
for $fileHandle.IO.lines -> $currentLine {
  # Utilize the "$currentLine" variable which holds a string.
}

$fileHandle.close;

使用檔案控制代碼可以重複利用資源,但另一方面也要求程式設計師管理它。如果提供的優勢不值得這些花費,上面提到的函式可以直接在以字串形式表示的檔名上工作。

可以透過指定檔名作為字串並呼叫 `IO.slurp` 來讀取完整的檔案內容。

my $fileName;
my $fileContents;

$fileName     = "path/to/data.txt";
$fileContents = $fileName.IO.slurp;

如果這種面向物件的 approach 不適合你的風格,等效的程式化變體是

my $fileName;
my $fileContents;

$fileName     = "path/to/data.txt";
$fileContents = slurp $fileName;

以相同的模式,逐行處理不依賴於檔案控制代碼的方式是

my $fileName;
my $fileContents;

$fileName = "path/to/data.txt";

for $fileName.IO.lines -> $line {
  # Utilize the "$currentLine" variable, which holds a string.
}

請記住,可以選擇在不將檔名儲存在變數中的情況下插入它,這將進一步縮短以上程式碼段。相應地,傳輸完整的檔案內容可能會減少到

my $fileContents = "path/to/data.txt".IO.slurp;

my $fileContents = slurp "path/to/data.txt";

為了以更細粒度的級別訪問檔案,Raku 自然提供了透過 `readchars` 函式檢索指定數量的字元的功能,該函式接受要讀取的字元數量,並返回表示獲得資料的字串。

my $fileName;
my $fileHandle;
my $charactersFromFile;

$fileName   = "path/to/data.txt";
$fileHandle = open $fileName, :r;

# Read eight characters from the file into a string variable.
$charactersFromFile = $fileHandle.readchars(8);

# Perform some action with the "$charactersFromFile" variable.

$fileHandle.close;

寫入檔案

[編輯 | 編輯原始碼]

關閉檔案

[編輯 | 編輯原始碼]
  1. 廣義化到與其他 IO 物件(如流、套接字等)互動的IO 控制代碼

外部資源

[編輯 | 編輯原始碼]

Perl 世界

[編輯 | 編輯原始碼]

Inline::Perl5

[編輯 | 編輯原始碼]

Inline::Perl5 是一個模組,用於執行 Perl 5 程式碼並在 Perl 6 中訪問 Perl 5 模組。

參見 https://github.com/niner/Inline-Perl5/

華夏公益教科書