Haskell入門


 

Haskell入門

各就各位,預備!

好 的,出發!如果你就是那種從不看說明書的不良人士,我推薦你還是回頭看一下簡介的最后一節。那里面講了這個教程中你需要用到的工具及基本用法。我們首先要 做的就是進入ghc的交互模式,接着就可以調幾個函數小體驗一把haskell了。打開控制台,輸入ghci,你會看到如下歡迎信息

 

GHCi, version 6.8.2: http://www.haskell.org/ghc/  
:? for help Loading package base ... linking ... done.
Prelude>

 

恭喜,您已經進入了ghci!目前它的命令行提示是prelude>,不過它在你裝載什么東西后會變的比較長。免得礙眼,我們輸入個:set prompt "ghci> "把它改成ghci>

 

如下是一些簡單的運算

 

ghci> 2 + 15  17   
ghci> 49 * 100 4900
ghci> 1892 - 1472 420
ghci> 5 / 2 2.5
ghci>

 

很簡單。也可以在一行中使用多個運算符,按照運算符優先級執行計算,使用括號可以更改優先級次序。

 

ghci> (50 * 100) - 4999   
1
ghci> 50 * 100 - 4999
1
ghci> 50 * (100 - 4999)
-244950

 

很酷么?嗯,我承認不。處理負數時會有個小陷阱:執行5 * -3會使ghci報錯。所以說,使用負數時最好將其置於括號之中,像5*(-3)就不會有問題。

 

邏輯運算也同樣直白,你也許知道,&&指邏輯與,||指邏輯或,not指邏輯否。

 

ghci> True && False   
False
ghci> True && True
True
ghci> False || True
True
ghci> not False
True
ghci> not (True && True)
False

 

相等性可以這樣判定

 

ghci> 5 == 5   
True
ghci> 1 == 0
False
ghci> 5 /= 5
False
ghci> 5 /= 4
True
ghci> "hello" == "hello"
True

 

執行5+"llama"或者5==True會怎樣?好的,一個大大的報錯等着你。

 

No instance for (Num [Char])   
arising from a use of `+' at :1:0-9
Possible fix: add an instance declaration for (Num [Char])
In the expression: 5 + "llama"
In the definition of `it': it = 5 + "llama"

 

Yikes!ghci 提示說"llama"並不是數值類型,所以它不知道該怎樣才能給它加上5。即便是“four”甚至是“4”也不可以,haskel不拿它當數值。執行 True==5, ghci就會提示類型不匹配。+運算符要求兩端都是數值,而==運算符僅對兩個可比較的值可用。這就要求他們的類型都必須一致,蘋果和橙子就無法做比較。 我們會在后面深入地理解類型的概念。Note:5+4.0是可以執行的,5既可以做被看做整數也可以被看做浮點數,但4.0則不能被看做整數。

也 許你並未察覺,不過從始至終我們一直都在使用函數。*就是一個將兩個數相乘的函數,就像三明治一樣,用兩個參數將它夾在中央,這被稱作中綴函數。而其他大 多數不能與數夾在一起的函數則被稱作前綴函數。絕大部分函數都是前綴函數,在接下來我們就不多做甄別。大多數命令式編程語言中的函數調用形式通常就是函數 名,括號,由逗號分隔的參數表。而在haskell中,函數調用的形式是函數名,空格,空格分隔的參數表。簡單據個例子,我們調用haskell中最無聊 的函數:

 

ghci> succ 8   
9

 

succ函數返回一個數的后繼(successor, 在這里就是8后面那個數,也就是9。譯者注)。如你所見,通過空格將函數與參數分隔。調用多個參數的函數也是同樣容易,min和max接受兩個可比較大小的參數,並返回較大或者較小的那個數。

 

ghci> min 9 10   
9
ghci> min 3.4 3.2
3.2
ghci> max 100 101
101

 

函數調用擁有最高的優先級,如下兩句是等效的

 

ghci> succ 9 + max 5 4 + 1   
16
ghci> (succ 9) + (max 5 4) + 1
16

 

若要取9乘10的后繼,succ 9*10是不行的,程序會先取9的后繼,然后再乘以10得100。正確的寫法應該是succ(9*10),得91。如果某函數有兩個參數,也可以用 ` 符號將它括起,以中綴函數的形式調用它。例如取兩個整數相除所得商的div函數,div 92 10可得9,但這種形式不容易理解:究竟是哪個數是除數,哪個數被除?使用中綴函數的形式 92 `div` 10 就更清晰了。從命令式編程走過來的人們往往會覺得函數調用與括號密不可分,在C中,調用函數必加括號,就像foo(),bar(1),或者 baz(3,"haha")。而在haskell中,函數的調用必使用空格,例如bar (bar 3),它並不表示以bar和3兩個參數去調用bar,而是以bar 3所得的結果作為參數去調用bar。在C中,就相當於bar(bar(3))`。

啟蒙:你的第一個函數

 

在前一節中我們簡單介紹了函數的調用,現在讓我們編寫我們自己的函數!打開你最喜歡的編輯器,輸入如下代碼,它的功能就是將一個數字乘以2.

 

doubleMe x = x + x 

 

函數的聲明與它的調用形式大體相同,都是先函數名,后跟由空格分隔的參數表。但在聲明中一定要在 = 后面定義函數的行為。

 

保存為baby.hs或任意名稱,然后轉至保存的位置,打開ghci,執行:l baby.hs。這樣我們的函數就裝載成功,可以調用了。

 

ghci> :l baby   
[1 of 1] Compiling Main ( baby.hs, interpreted )
Ok, modules loaded: Main.
ghci> doubleMe 9
18
ghci> doubleMe 8.3
16.6

 

+運算符對整數和浮點都可用(實際上所有有數字特征的值都可以),所以我們的函數可以處理一切數值。聲明一個包含兩個參數的函數如下:

 

doubleUs x y = x*2 + y*2  

 

很簡單。將其寫成doubleUs x y = x + x + y + y也可以。測試一下(記住要保存為baby.hs並到ghci下邊執行:l baby.hs)

 

ghci> doubleUs 4 9  26   
ghci> doubleUs 2.3 34.2 73.0
ghci> doubleUs 28 88 + doubleMe 123
478

 

你可以在其他函數中調用你編寫的函數,如此一來我們可以將doubleMe函數改為:

 

doubleUs x y = doubleMe x + doubleMe y  

這 種情形在haskell下邊十分常見:編寫一些簡單的函數,然后將其組合,形成一個較為復雜的函數,這樣可以減少重復工作。設想若是哪天有個數學家驗證說 2應該是3,我們只需要將doubleMe改為x+x+x即可,由於doubleUs調用到doubleMe,於是整個程序便進入了2即是3的古怪世界。

 

haskell中的函數並沒有順序,所以先聲明doubleUs還是先聲明doubleMe都是同樣的。如下,我們編寫一個函數,它將小於100的數都乘以2,因為大於100的數都已經足夠大了!

 

doubleSmallNumber x = if x > 100                           
then x
else x*2

 

接 下來介紹haskell的if語句。你也許會覺得和其他語言很像,不過存在一些不同。haskell中if語句的else部分是不可省略。在命令式語言 中,你可以通過if語句來跳過一段代碼,而在haskell中,每個函數和表達式都要返回一個結果。對於這點我覺得將if語句置於一行之中會更易理解。 haskell 中的if語句的另一個特點就是它其實是個表達式,表達式就是返回一個值的一段代碼:5是個表達式,它返回5;4+8是個表達式;x+y也是個表達式,它返 回x+y的結果。正由於else是強制的,if語句一定會返回某個值,所以說if語句也是個表達式。如果要給剛剛定義的函數的結果都加上1,可以如此修 改:

 

doubleSmallNumber' x = (if x > 100 then x else x*2) + 1 

 

若是去掉括號,那就會只在小於100的時候加1。注意函數名最后的那個單引號,它沒有任何特殊含義,只是一個函數名的合法字符罷了。通常,我們使用單引號來區分一個稍經修改但差別不大的函數。定義這樣的函數也是可以的:

 

conanO'Brien = "It's a-me, Conan O'Brien!"  

 

在 這里有兩點需要注意。首先就是我們沒有大寫conan的首字母,因為首字母大寫的函數是不允許的,稍后我們將討論其原因;另外就是這個函數並沒有任何參 數。沒有參數的函數通常被稱作“定義”(或者“名字”),一旦定義,conanO'Brien就與字符串"It's a-me, Conan O'Brien!"完全等價,且它的值不可以修改。

List入門

在 Haskell中,List就像現實世界中的購物單一樣重要。它是最常用的數據結構,並且十分強大,靈活地使用它可以解決很多問題。本節我們將對 List,字符串和list comprehension有個初步了解。 在Haskell中,List是一種單類型的數據結構,可以用來存儲多個類型相同的元素。我們可以在里面裝一組數字或者一組字符,但不能把字符和數字裝在 一起。

 

Note:在ghci下,我們可以使用let關鍵字來定義一個常量。在ghci下執行let a =1與在腳本中編寫a=1是等價的。

 

ghci> let lostNumbers = [4,8,15,16,23,48]   
ghci> lostNumbers
[4,8,15,16,23,48]

 

如你所見,一個List由方括號括起,其中的元素用逗號分隔開來。若試圖寫[1,2,'a',3,'b','c',4]這樣的List,Haskell就會報出這幾個字符不是數字的錯誤。字符串實際上就是一組字符的List,"Hello"只是['h','e','l','l','o']的語法糖而已。所以我們可以使用處理List的函數來對字符串進行操作。 將兩個List合並是很常見的操作,這可以通過++運算符實現。

 

ghci> [1,2,3,4] ++ [9,10,11,12]    
[1,2,3,4,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world"
ghci> ['w','o'] ++ ['o','t']
"woot"

 

在 使用++運算符處理長字符串時要格外小心(對長List也是同樣),Haskell會遍歷整個的List(++符號左邊的那個)。在處理較短的字符串時問 題還不大,但要是在一個5000萬長度的List上追加元素,那可得執行好一會兒了。所以說,用:運算符往一個List前端插入元素會是更好的選擇。

 

ghci> 'A':" SMALL CAT"   
"A SMALL CAT"
ghci> 5:[1,2,3,4,5]
[5,1,2,3,4,5]

 

:運算符可以連接一個元素到一個List或者字符串之中,而++運算符則是連接兩個List。若要使用++運算符連接單個元素到一個List之中,就用方括號把它括起使之成為單個元素的List。[1,2,3]實際上是1:2:3:[]的語法糖。[]表示一個空List,若要從前端插入3,它就成了[3],再插入2,它就成了[2,3],以此類推。

 

Note:[],[[]],[[],[],[]]是不同的。第一個是一個空的List,第二個是含有一個空List的List,第三個是含有三個空List的List。

 

若是要按照索引取得List中的元素,可以使用!!運算符,索引的下標為0。

 

ghci> "Steve Buscemi" !! 6   
'B'
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1
33.2

 

但你若是試圖在一個只含有4個元素的List中取它的第6個元素,就會報錯。要小心!

 

List同樣也可以用來裝List,甚至是List的List的List:

 

ghci> let b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]   
ghci> b
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b ++ [[1,1,1,1]]
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
ghci> [6,6,6]:b
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci> b !! 2
[1,2,2,3,4]

 

List 中的List可以是不同長度,但必須得是相同的類型。如不可以在List中混合放置字符和數組相同,混合放置數值和字符的List也是同樣不可以的。當 List內裝有可比較的元素時,使用 > 和 >=可以比較List的大小。它會先比較第一個元素,若它們的值相等,則比較下一個,以此類推。

 

ghci> [3,2,1] > [2,1,0]   
True
ghci> [3,2,1] > [2,10,100]
True
ghci> [3,4,2] > [3,4]
True
ghci> [3,4,2] > [2,4]
True
ghci> [3,4,2] == [3,4,2]
True

 

還可以對List做啥?如下是幾個常用的函數:

 

head返回一個List的頭部,也就是List的首個元素。

 

ghci> head [5,4,3,2,1]  
5

 

tail返回一個LIst的尾部,也就是List除去頭部之后的部分。

 

ghci> tail [5,4,3,2,1]   
[4,3,2,1]

 

last返回一個LIst的最后一個元素。

 

ghci> last [5,4,3,2,1]   
1

 

init返回一個LIst出去最后一個元素的部分。

 

ghci> init [5,4,3,2,1]   
[5,4,3,2]

 

如果我們把List想象為一頭怪獸,那這就是它的樣子:

試一下,若是取一個空List的head又會怎樣?

 

ghci> head []   
*** Exception: Prelude.head: empty list

 

omg,它翻臉了!怪獸壓根就不存在,head又從何而來?在使用head,tail,last和init時要小心別用到空的List上,這個錯誤不會在編譯時被捕獲。所以說做些工作以防止從空List中取值會是個好的做法。

 

length返回一個List的長度。

 

ghci> length [5,4,3,2,1]   
5

 

null檢查一個List是否為空。如果是,則返回True,否則返回False。應當避免使用xs==[]之類的語句來判斷List是否為空,使用null會更好。

 

ghci> null [1,2,3]   
False
ghci> null []
True

 

reverse將一個List反轉

 

ghci> reverse [5,4,3,2,1]   
[1,2,3,4,5]

 

take返回一個List的前幾個元素,看:

 

ghci> take 3 [5,4,3,2,1]   
[5,4,3]
ghci> take 1 [3,9,3]
[3]
ghci> take 5 [1,2]
[1,2]
ghci> take 0 [6,6,6]
[]

 

如上,若是圖取超過List長度的元素個數,只能得到原List。若take 0個元素,則會得到一個空List!drop與take的用法大體相同,它會刪除一個List中的前幾個元素。

 

ghci> drop 3 [8,4,2,1,5,6]   
[1,5,6]
ghci> drop 0 [1,2,3,4]
[1,2,3,4]
ghci> drop 100 [1,2,3,4]
[]

 

maximum返回一個List中最大的那個元素。miniimun返回最小的。

 

ghci> minimum [8,4,2,1,5,6]   
1
ghci> maximum [1,9,2,3,4]
9

 

sum返回一個List中所有元素的和。product返回一個List中所有元素的積。

 

ghci> sum [5,2,1,6,3,2,5,7]   
31
ghci> product [6,2,1,2]
24
ghci> product [1,2,5,6,7,9,2,0]
0

 

elem判斷一個元素是否在包含於一個List,通常以中綴函數的形式調用它。

 

ghci> 4 `elem` [3,4,5,6]   
True
ghci> 10 `elem` [3,4,5,6]
False

 

這就是幾個基本的List操作函數,我們會在往后的一節中了解更多的函數。

德州區間

該 怎樣得到一個包含1到20之間所有數的List呢?我們完全可以用手把它全打出來,但顯而易見,這並不是完美人士的方案,他們都用區間(Range)。 Range是構造List方法之一,而其中的值必須是可枚舉的,像1、2、3、4...字符同樣也可以枚舉,字母表就是A..Z所有字符的枚舉。而名字就 不可以枚舉了,"john"后面是誰?我不知道。

 

要得到包含1到20中所有自然數的List,只要[1..20]即可,這與用手寫[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]是完全等價的。其實用手寫一兩個還不是什么大事,但若是手寫一個非常長的List那就一定是笨得可以了。

 

ghci> [1..20]   
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> ['K'..'Z']
"KLMNOPQRSTUVWXYZ"

 

Range很cool,允許你申明一個步長。要得到1到20間所有的偶數或者3的倍數該怎樣?

 

ghci> [2,4..20]   
[2,4,6,8,10,12,14,16,18,20]
ghci> [3,6..20]
[3,6,9,12,15,18]

 

僅需用逗號將前兩個元素隔開,再標上上限即可。盡管Range很聰明,但它恐怕還滿足不了一些人對它的期許。你就不能通過[1,2,4..100]這樣的語句來獲得所有2的冪。一方面是因為步長只能標明一次,另一方面就是僅憑前幾項,數組的后項是不能確定的。要得到20到1的List,[20..1]是不可以的。必須得[20,19..1]。在Range中使用浮點數要格外小心!出於定義的原因,浮點數並不精確。若是使用浮點數的話,你就會得到如下的糟糕結果

 

ghci> [0.1, 0.3 .. 1]   
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]

 

我的建議就是避免在Range中使用浮點數。

 

你也可以不標明Range的上限,從而得到一個無限長度的List。在后面我們會講解關於無限List的更多細節。取前24個13的倍數該怎樣?恩,你完全可以[13,26..24*13],但有更好的方法:take 24 [13,26..]

 

由 於Haskell是惰性的,它不會對無限長度的List求值,否則會沒完沒了的。它會等着,看你會從它那兒取多少。在這里它見你只要24個元素,便欣然交 差。如下是幾個生成無限List的函數cycle接受一個List做參數並返回一個無限List。如果你只是想看一下它的運算結果而已,它會運行個沒完 的。所以應該在某處划好范圍。

 

ghci> take 10 (cycle [1,2,3])   
[1,2,3,1,2,3,1,2,3,1]
ghci> take 12 (cycle "LOL ")
"LOL LOL LOL "

 

repeat接受一個值作參數,並返回一個僅包含該值的無限List。這與用cycle處理單元素List差不多。

 

ghci> take 10 (repeat 5)   
[5,5,5,5,5,5,5,5,5,5]

 

其實,你若只是想得到包含相同元素的List,使用replicate會更簡單,如replicate 3 10,得[10,10,10]

我是List Comprehension

學過數學的你對集合的comprehension(Set Comprehension)概念一定不會陌生。通過它,可以從既有的集合中按照規則產生一個新集合。前十個偶數的set comprehension可以表示為,豎線左端的部分是輸出函數,x是變量,N是輸入集合。在haskell下,我們可以通過類似take 10 [2,4..]的代碼來實現。但若是把簡單的乘2改成更復雜的函數操作該怎么辦呢?用list comprehension,它與set comprehension十分的相似,用它取前十個偶數輕而易舉。這個list comprehension可以表示為:

 

ghci> [x*2 | x <- [1..10]]   
[2,4,6,8,10,12,14,16,18,20]

 

如你所見,結果正確。給這個comprehension再添個限制條件(predicate),它與前面的條件由一個逗號分隔。在這里,我們要求只取乘以2后大於等於12的元素。

 

ghci> [x*2 | x <- [1..10], x*2 >= 12]   
[12,14,16,18,20]

 

cool,靈了。若是取50到100間所有除7的余數為3的元素該怎么辦?簡單:

 

ghci> [ x | x <- [50..100], x `mod` 7 == 3]   
[52,59,66,73,80,87,94]

 

成 功!從一個List中篩選出符合特定限制條件的操作也可以稱為過濾(flitering)。即取一組數並且按照一定的限制條件過濾它們。再舉個例子 吧,假如我們想要一個comprehension,它能夠使list中所有大於10的奇數變為“BANG”,小於10的奇數變為“BOOM”,其他則統統 扔掉。方便重用起見,我們將這個comprehension置於一個函數之中。

 

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]  

 

這個comprehension的最后部分就是限制條件,使用odd函數判斷是否為奇數:返回True,就是奇數,該List中的元素才被包含。

 

ghci> boomBangs [7..13]   
["BOOM!","BOOM!","BANG!","BANG!"]

 

也可以加多個限制條件。若要達到10到20間所有不等於13,15或19的數,可以這樣:

 

ghci> [ x | x <- [10..20], x /= 13, x /= 15, x /= 19]   
[10,11,12,14,16,17,18,20]

 

除了多個限制條件之外,從多個List中取元素也是可以的。這樣的話comprehension會把所有的元素組合交付給我們的輸出函數。在不過濾的前提 下,取自兩個長度為4的集合的comprehension會產生一個長度為16的List。假設有兩個List,[2,5,10][8,10,11], 要取它們所有組合的積,可以這樣:

 

ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]   
[16,20,22,40,50,55,80,100,110]

 

意料之中,得到的新List長度為9。若只取乘積為50的結果該如何?

 

ghci> [ x*y | x <-[2,5,10], y <- [8,10,11], x*y > 50]   
[55,80,100,110]

 

取個包含一組名詞和形容詞的List comprehension吧,寫詩的話也許用得着。

 

ghci> let nouns = ["hobo","frog","pope"]   
ghci> let adjectives = ["lazy","grouchy","scheming"]
ghci> [adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
["lazy hobo","lazy frog","lazy pope","grouchy hobo","grouchy frog", "grouchy pope","scheming hobo",
"scheming frog","scheming pope"]

 

明白!讓我們編寫自己的length函數吧!就叫做length'!

 

length' xs = sum [1 | _ <- xs]  

 

_ 表示我們並不關心從List中取什么值,與其弄個永遠不用的變量,不如直接一個_。這個函數將一個List中所有元素置換為1,並且使其相加求和。得到的 結果便是我們的List長度。友情提示:字符串也是List,完全可以使用list comprehension來處理字符串。如下是個除去字符串中所有非大寫字母的函數:

 

removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]  

 

測試一下:

 

ghci> removeNonUppercase "Hahaha! Ahahaha!"   
"HA"
ghci> removeNonUppercase "IdontLIKEFROGS"
"ILIKEFROGS"

 

在這里,限制條件做了所有的工作。它說:只有在['A'..'Z']之間的字符才可以被包含。

 

若操作含有List的List,使用嵌套的List comprehension也是可以的。假設有個包含許多數值的List的List,讓我們在不拆開它的前提下除去其中的所有奇數:

 

ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]   
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

 

將List Comprehension分成多行也是可以的。若非在GHCI之下,還是將List Comprehension分成多行好,尤其是需要嵌套的時候。

Tuple

從 某種意義上講,Tuple(元組)很像List--都是將多個值存入一個個體的容器。但它們卻有着本質的不同,一組數字的List就是一組數字,它們的類 型相 同,且不關心其中包含元素的數量。而Tuple則要求你對需要組合的數據的數目非常的明確,它的類型取決於其中項的數目與其各自的類型。Tuple中的項 由括號括起,並由逗號隔開。

 

另外的不同之處就是Tuple中的項不必為同一類型,在Tuple里可以存入多類型項的組合。

 

動腦筋,在haskell中表示二維向量該如何?使用List是一種方法,它倒也工作良好。若要將一組向量置於一個List中來表示平面圖形又該怎樣?我們可以寫類似[[1,2],[8,11],[4,5]]的代碼來實現。但問題在於,[[1,2],[8,11,5],[4,5]]也 是同樣合法的,因為其中元素的類型都相同。盡管這樣並不靠譜,但編譯時並不會報錯。然而一個長度為2的Tuple(也可以稱作序對,Pair),是一個獨 立的類 型,這便意味着一個包含一組序對的List不能再加入一個三元組,所以說把原先的方括號改為圓括號使用Tuple會 更好:[(1,2),(8,11),(4,5)]。若試圖表示這樣的圖形:[(1,2),(8,11,5),(4,5)],就會報出以下的錯誤:

 

Couldn't match expected type `(t, t1)'   
against inferred type `(t2, t3, t4)'
In the expression: (8, 11, 5)
In the expression: [(1, 2), (8, 11, 5), (4, 5)]
In the definition of `it': it = [(1, 2), (8, 11, 5), (4, 5)]

 

這告訴我們說程序在試圖將序對和三元組置於同一List中,而這是不允許的。同樣[(1,2),("one",2)]這樣的List也不行,因為 其中的第一個Tuple是一對數字,而第二個Tuple卻成了一個字符串和一個數字。Tuple可以用來儲存多個數據,如,我們要表示一個人的名字與年 齡,可以使用這樣的Tuple:("Christopher", "Walken", 55)。從這個例子里也可以看出,Tuple中也可以存儲List。

 

使用Tuple前應當事先明確一條數據中應該由多少個項。每個不同長度的Tuple都是獨立的類型,所以你就不可以寫個函數來給它追加元素。而唯一能做的,就是通過函數來給一個List追加序對,三元組或是四元組等內容。

 

可以有單元素的List,但Tuple不行。想想看,單元素的Tuple本身就只有一個值,對我們又有啥意義?不靠譜。

 

同List相同,只要其中的項是可比較的,Tuple也可以比較大小,只是你不可以像比較不同長度的List那樣比較不同長度的Tuple。如下是兩個有用的序對操作函數:

 

fst返回一個序對的首項。

 

ghci> fst (8,11)   
8
ghci> fst ("Wow", False)
"Wow"

 

snd返回序對的尾項。

 

ghci> snd (8,11)   
11
ghci> snd ("Wow", False)
False

 

Note:這兩個函數僅對序對有效,而不能應用於三元組,四元組和五元組之上。稍后,我們將過一遍從Tuple中取數據的所有方式。

 

有個函數很cool,它就是zip。它可以用來生成一組序對(Pair)的List。它取兩個List,然后將它們交叉配對,形成一組序對的List。它很簡單,卻很實用,尤其是你需要組合或是遍歷兩個List時。如下是個例子:

 

ghci> zip [1,2,3,4,5] [5,5,5,5,5]   
[(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]

 

它把元素配對並返回一個新的List。第一個元素配第一個,第二個元素配第二個..以此類推。注意,由於序對中可以含有不同的類型,zip函數可能會將不同類型的序對組合在一起。若是兩個不同長度的List會怎么樣?

 

ghci> zip [5,3,2,6,2,7,2,5,4,6,6] ["im","a","turtle"]   
[(5,"im"),(3,"a"),(2,"turtle")]

 

較長的那個會在中間斷開,去匹配較短的那個。由於haskell是惰性的,使用zip同時處理有限和無限的List也是可以的:

 

ghci> zip [1..] ["apple", "orange", "cherry", "mango"]   
[(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")]

 

接下來考慮一個同時應用到List和Tuple的問題:如何取得所有三邊長度皆為整數且小於等於10,周長為24的直角三角形?首先,把所有三遍長度小於等於10的三角形都列出來:

 

ghci> let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]

 

剛才我們是從三個List中取值,並且通過輸出函數將其組合為一個三元組。只要在ghci下邊調用triangle,你就會得到所有三邊都小於等於 10的三角形。我們接下來給它添加一個限制條件,令其必須為直角三角形。同時也考慮上b邊要短於斜邊,a邊要短於b邊情況:

 

ghci> let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]

 

已經差不多了。最后修改函數,告訴它只要周長為24的三角形。

 

ghci> let rightTriangles' = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2, a+b+c == 24]   
ghci> rightTriangles'
[(6,8,10)]



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM