2: The Basics
1 about
github地址:https://github.com/gaorongchao/Perl6/tree/master/Using_perl6
如果發現任何錯誤和翻譯不當的地方,請告知,非常感謝。
2 第二章:基礎
Perl起源於一個致力於從文本文件中收集整理信息的編程語言。 現在Perl在文本處理方面仍然強大,Perl 5 是一個一般意義上的強力編程語言。 但是Perl 6更爲傑出。
假設你舉辦了一個乒乓球聯賽。 裁判告訴你比賽結果的格式:選手1 選手2 | 3:2,這意味着,選手1贏了3局,選手2贏了2局。 你需要一個腳本來,來統計選手贏了幾場比賽,贏了多少局,並以此決定最終冠軍的歸屬。
輸入文件格式如下(儲存在一個名爲“scores”的文件中):
Beth Ana Charlie Dave Ana Dave | 3:0 Charlie Beth | 3:1 Ana Beth | 2:3 Dave Charlie | 3:0 Ana Charlie | 3:1 Beth Dave | 0:3
第一行是所有選手的名字,後面跟着的是每場比賽的結果。 這裏有一個解決此問題的Perl 6 腳本:perl-1-1:
1: use v6; 2: 3: my $file = open 'scores'; 4: my @names = $file.get.words; 5: 6: my %matches; 7: my %sets; 8: 9: for $file.lines -> $line { 10: my ($pairing, $result) = $line.split(' | '); 11: my ($p1, $p2) = $pairing.words; 12: my ($r1, $r2) = $result.split(':'); 13: 14: %sets{$p1} += $r1; 15: %sets{$p2} += $r2; 16: 17: if $r1 > $r2 { 18: %matches{$p1}++; 19: } else { 20: %matches{$p2}++; 21: } 22: } 23: 24: my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse; 25: 26: for @sorted -> $n { 27: say "$n has won %matches{$n} matches and %sets{$n} sets"; 28: }
輸出文件:
Ana has won 2 matches and 8 sets Dave has won 2 matches and 6 sets Charlie has won 1 matches and 4 sets Beth has won 1 matches and 4 sets
每一個Perl6程序都需要以 “use v6”; 開始。這一行告訴編譯器Perl代碼的版本。 如果,你不小心用Perl5來運行這個程序,你將得到錯誤信息。
一個Perl6程序包含0行或者更多的語句。每一個語句以分號結尾,或者以大括號結尾:
1: my $file = open 'scores';
"my"聲明一個詞法變量。詞法變量只在當前代碼塊可用,從聲明開始到代碼塊的結束。 如果一個代碼塊始終沒有閉合,那麼該變量可以在剩下的所有地方使用。代碼塊的定義是: 一段在花括號\{\}之間的代碼。
一個變量名稱以“魔符”開始,魔符是指那些非字母,非數字的符號,比如:$,@,% 或者&- 或者有時出現的雙冒號“::”也是。 魔符指明瞭變量的結構類型,比如:它應該被當作一個值,還是多個值,還是一個子程序,等等。 跟在“魔符”後面的是“標識符”,標識符可以包含字母,數字和下劃線。在字母與字母之間, 你可以用短橫線“-”或者撇號“'”。所以“isn't”和“double-click”都是合法的變量標識符。
魔符“$”表明變量是一個數值變量,指明這個變量只存儲了一個值。
內建函數“open”打開了一個名爲“scores”的文件,然後返回一個文件句柄, 文件句柄是一個代表該文件的對象。 賦值符號“=”,把文件句柄賦值給左邊的變量,這也就是意味着變量$file現在存儲着文件句柄。
“scores”是字符串文本(string literal)。字符串(string)是一段純文本。 字符串文本(string literal)是直接出現在程序中的字符串。 在這一行中,它是提供給open函數的參數。
1: my @names = $file.get.words;
上面語句的右側調用了一種“方法”(一個命名的一系列行爲的集合體)獲取存儲在$file變量中的句柄。 被命名爲get的這一方法從文件中讀取並返回一行,去掉末尾的換行符。word同樣是一個方法,調用從 get中返回的一行文本。word這一方法,分解它的invocant(它要操作的字符串)成一系列的單詞。 這裏單詞的意思是不包含空格的字符串。它把字符串“Beth Ana Charlie Dave”分解成多個字符串 “Beth”,“Ana”,“Charlie”,“Dave”。
最後,這一字符串列表存儲到數組@names中。魔符@表明所聲明的變量是一個數組。數組存儲的是有序列表。
1: my %matches; 2: my %sets;
這兩行代碼聲明瞭兩個哈希。魔符“%”把變量標記爲哈希。哈希又稱爲散列:是無序的鍵-值對的集合。 在其他語言中又稱爲“哈希表”,“字典”或者“(map)圖” 你可以通過“鍵 $key”利用“%hash{$key}”來查詢在哈希表中的值。
在上述計分程序中,%matches記錄了每一位運動員贏的次數。%sets記錄了每一位運動員贏的局數。
魔符指出了默認訪問變量的方法。數組變量@是通過位置來訪問, 哈希變量%是通過“鍵”來訪問。 數值變量$表示一個大籮筐,這裏可以盛任何東西,也可以用任何方式來訪問。 一個數值變量可以包含一個複雜對象(compound object),比如:數組或者哈希; 魔符$表明它應該被當作一個單獨的值,儘管有可能它包含衆多的值(像一個數組或者哈希)。
1: for $file.lines ->$line{ 2: ... 3: }
“for”產生了一個由花括號界定運行範圍的循環,循環會遍歷列表中的每一個值。 $file.lines 從scores文件中讀取了很多行,除掉前面$file.get讀取的那一行以外,到最後一行。 循環依次將每一行的值賦予$line變量。
第一次迭代$line將包含字符串 “Ana Dave | 3:0”;第二次迭代$line將包含字符串“Charlie Beth | 3:1”,等等。
1: my ($pairing,$result) = $line.split('|');
“my”可以同時聲明多個變量。在賦值符號(=)的右側調用了一個名爲“split”的方法,它把“|”當作參數。
split把他作用的內容以|爲分割點,分割成一系列的字符串。所以如果你用“|”作爲粘合符號,把這些字符串粘合。 那麼你將得到原始字符串。
$pairing 得到返回列表的第一個元素,$result 得到第二個。
當處理完第一行以後,$pairing 包含“Ana Dave”這個字符串,$result包含“3:0”。
下面兩行是同樣的模式:
1: my ($p1,$p2) = $pairing.words; 2: my ($r1,$r2) = $result.split(':');
第一行代碼提取並保存兩位運動員的姓名到$p1和$p2這兩個變量中。
第二行提取每一位運動員的比賽結果,並且分別保存到$r1和$r2中。
上面程序運行以後,下面列出每一個變量包含的值:
Table2.1:Contents of Variables
1: Variable Contents 2: $line 'Ana Dave | 3:0' 3: $pairing 'Ana Dave' 4: $result '3:0' 5: $p1 'Ana' 6: $p2 'Dave' 7: $r1 '3' 8: $r2 '0'
程序接下來統計了每一位運動員贏的局數:
1: %sets{$p1} += $r1; 2: %sets{$p2} += $r2;
這是下面的簡寫:
1: %sets{$p1} = %sets{$p1}+$r1; 2: %sets{$p2} = %sets{$p2}+$r2;
+= $r1 的含義是:把左側的變量加上$r1. 在第一次迭代的時候%sets{$p1}還沒有設置類型, 所以默認爲一個稱爲“Any”的特殊值。 加法和遞增操作符作爲一個數字操作符,把Any當作一個值爲0的數字。 所以這個字符串自動轉換爲數字。
在這兩行代碼執行以前,%sets 是空的。 在hash中添加一個完全不存在的元素,將會使這個元素立馬在哈希中存在。 並且賦予初始值爲0.(這被稱爲autovivification)。 當這兩行第一次運行以後,%sets包含“ 'Ana'=> 3,'Dave'=>0”。 (胖箭頭=>把鍵值對分開)
1: if $r1>$r2{ 2: %matches{$p1}++; 3: } else { 4: %matches{$p2}++; 5: }
如果$r1 的值大於$r2的值,那麼%matches{$p1}增加1。 如果$r1 的值小於$r2的值,那麼%matches{$p2}增加1。 和前面的+=的例子一樣,如果哈希中沒有存在這個值, 那麼這個哈希值將會因爲自增符號而自動生成。
$thing++ 是$thing +=1的縮寫。 需要注意的一點是,$thing++這個表達式返回的$thing的值是沒有增加前的值。 而不是增加以後的。在其他很多的編程語言中你可以把++前置。這樣得到的增加 以後的返回值。my $x=1; say ++$x 輸出2。
1: my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
這一行代碼包含三個獨立的過程。首先調用數組的sort方法。 但是默認的排序方法是按照內容排序的。爲了按照贏得多少的順序輸出運動員的姓名, 我們的程序必須按照運動員的成績來排序,而不是用他們的名字。 sort方法的參數一個代碼塊,用來把數組元素(運動員的名字)轉化爲用來排序的數據。 數組元素是通過“$_”變量來傳遞的。
你在前面已經看到過代碼塊:不論是“for loop-> {…}”還是在代碼塊中的if語句。 代碼塊是:一個獨立的帶有 signature(the ->$line 的部分)Perl6代碼。 更多內容請參照sec::signatures。
按照運動員成績排序的最簡單的方法是@names.sort({ %matches{$_} }), 這是按照運動員贏的次數決定的。但是問題是,Ana 和Dave都贏了2次。 最簡單的排序方式並沒有考慮到每個人贏的局數,而這個正是決定誰贏的循環賽的第二個評判標準。
如果兩個數組元素擁有相同的值, sort按照發現他們的先後順序放置他們。 計算機科學家們稱之爲:穩定排序。 這個程序充分利用了Perl6排序的相關特性來利用兩次排序來實現目標: 第一次按照贏取局數的多少排序(也就是次要的冠軍評判標準),然後按照贏取的次數來排序。
在第一次排序以後,運動員的名字順序是這樣的:Beth Charlie Dave Ana。 在第二次排序以後,順序依然一樣。因爲沒有人贏了更少的次數,但是贏了更多的局數。 這種情況是很正常的,特別是在大型的循環賽中。
sort是按照升序來排序,也就是從小到大排序。 但這與我們的期望是相反的。所以,我們的程序在第二次排序以後,又調用了.reverse方法。 最後把結果存入到@sorted數組中。
1: for @sorted -> $n { 2: say "$n has won %matches{$n} matches and %sets{$n} sets"; 3: }
爲了輸出運動員的名字和他們的成績,我們對@sorted數組進行循環。 依次將運動員名字賦值給$n。我們可以這樣來讀代碼“對數組sorted中的每一個元素, 賦值給$n,然後執行下面的代碼塊”。 然後用say 進行標準輸出(也就是輸出到屏幕)。 然後後面自動加上換行符。 如果你不想在最後加上換行符,那就用print函數。
當你運行本程序的時候,你會發現,say並不會逐字的輸出所有內容。 在$n的位置上,它將會打印變量$n的內容:也就是存儲在$n中的運動員的名字。 這種自動替換被稱爲“interpolation(變量內插)” 這種變量內插只會發生在雙引號內,而不會發生在單引號內。
1: my $names = 'things'; 2: say 'Do not call me $name'; # Do not call me $names 3: say "Do not call me $name"; # Do not call me things
在Perl6中,雙引號不僅能夠內插魔符$的變量,同時也可以內插在花括號內的代碼塊。 因爲任意的Perl代碼都可以通過花括號出現,所以可以通過把數組和哈希放在花括號內 實現內插的效果。
數組放在花括號內插以後,兩個元素之間會插入一個空格。 哈希放在花括號內插以後,以多行的形式顯示,每一行包括一個鍵,然後跟着一個製表符。 然後緊跟這鍵對應的值,最後加上換行符。
1: say "Math: {1+2}"; # Match: 3 2: my @people = <Luke Mattew Mark>; 3: say "The synoptics are: {@people}"; # The synoptics are :Luck Matthew Mark 4: say "{%sets}"; # 接前面網球循環賽 5: # Charlie 4 6: # Dave 6 7: # Ana 8 8: # Beth 4
當數組和哈希變量直接出現在雙引號之間的時候(並沒有在花括號之間),那麼只有在它們的名字 後面緊跟方括號的時候纔會變量內插。當然你也可以在變量名和postcircumfix之間調用一種 方法來實現變量內插。
1: my @flavours = <vanilla peach>; 2: say "we have @flavours"; # we have @flavours 3: say "we have @flavours[0]" # we have vanilla 4: # so-called "Zen slice" 5: say "we have @flavours[]"; # we have Vanilla peach 6: 7: # method calls ending in postcircumfix 8: say "we have @flavours.sort()"; # we have peach vanilla 9: 10: # chained method calls: 11: say "we have @flavours.sort.join(',')"; # we have peach ,vanilla