在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 從零開始
函數(shù)的語法
Resource
Zippers 資料結(jié)構(gòu)
函數(shù)式地思考來解決問題
簡介
遞回
輸入與輸出
FAQ
Types and Typeclasses
再來看看更多 Monad
來看看幾種 Monad
高階函數(shù)
構(gòu)造我們自己的 Types 和 Typeclasses
從零開始
Functors, Applicative Functors 與 Monoids
模組 (Modules)

從零開始

準備好了嗎?

http://wiki.jikexueyuan.com/project/haskell-guide/images/startingout.png" alt="" />

準備來開始我們的旅程!如果你就是那種從不看說明書的人,我推薦你還是回頭看一下簡介的最后一節(jié)。那里面講了這個教學中你需要用到的工具及基本用法。我們首先要做的就是進入 ghc 的互動模式,接著就可以寫幾個函數(shù)體驗一下 Haskell 了。打開終端機,輸入 ghci,你會看到下列歡迎訊息:

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

恭喜您已經(jīng)進入了 ghci 了!目前它的命令列提示是 prelude>,不過它在你裝載一些模組之后會變的比較長。為了美觀起見,我們會輸入指令 :set prompt "ghci> " 把它改成 ghci>。

首先來看一些簡單的運算

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

很簡單吧!你也可以在一行中使用多個運算子,他們會按照運算子優(yōu)先順序執(zhí)行計算,而使用括號可以改變執(zhí)行的優(yōu)先順序。

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

但注意處理負數(shù)的時候有個小陷阱:我們執(zhí)行 5 * -3 會 ghci 會回報錯誤。所以說,使用負數(shù)時最好將其置于括號之中,像 5*(-3) 就不會有問題。

要進行布林代數(shù) (Boolean Algebra) 的演算也是很直覺的。你也許早就會猜,&& 指的是布林代數(shù)上的 AND,而 || 指的是布林代數(shù)上的 OR,not 會把 True 變成 False,False 變成 True。

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

那執(zhí)行 5+"llama" 或者 5==True 會怎樣? 如果我們真的試著在 ghci 中跑,會得到下列的錯誤訊息:

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"

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

http://wiki.jikexueyuan.com/project/haskell-guide/images/ringring.png" alt="" />

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

ghci>?succ?8
9

succ 函數(shù)返回一個數(shù)的后繼 (successor)。而且如你所見,在 Haskell 中是用空格來將函數(shù)與參數(shù)分隔的。至于呼叫多個參數(shù)的函數(shù)也很容易,minmax 接受兩個可比較大小的參數(shù),并返回較大或者較小的那個數(shù)。

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

函數(shù)呼叫擁有最高的優(yōu)先順序,如下兩句是等效的

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

若要取 9 乘 10 的后繼,succ 9*10 是不行的,程式會先取 9 的后繼,然后再乘以 10 得 100。正確的寫法應(yīng)該是 succ(9*10),得 91。如果某函數(shù)有兩個參數(shù),也可以用 ` 符號將它括起,以中綴函數(shù)的形式呼叫它。

例如取兩個整數(shù)相除所得商的 div 函數(shù), div 92 10 可得 9,但這種形式不容易理解:究竟是哪個數(shù)是除數(shù),哪個數(shù)被除?使用中綴函數(shù)的形式 92 `div` 10 就更清晰了。

從命令式程式語言走過來的人們往往會覺得函數(shù)呼叫與括號密不可分,在 C 中,呼叫函數(shù)必加括號,就像 foo(), bar(1),或者 baz(3,"haha")。而在 Haskell 中,函數(shù)的呼叫使用空格,例如 bar (bar 3),它并不表示以 bar 和 3 兩個參數(shù)去呼叫 bar,而是以 bar 3 所得的結(jié)果作為參數(shù)去呼叫 bar。在 C 中,就相當于 bar(bar(3))。

初學者的第一個函數(shù)

在前一節(jié)中我們簡單介紹了函數(shù)的呼叫,現(xiàn)在讓我們編寫我們自己的函數(shù)!打開你最喜歡的編輯器,輸入如下程式碼,它的功能就是將一個數(shù)字乘以 2。

doubleMe?x?=?x?+?x

函數(shù)的聲明與它的呼叫形式大致相同,都是先函數(shù)名,后跟由空格分隔的參數(shù)表。但在聲明中一定要在 = 后面定義函數(shù)的行為。

保存為 baby.hs 或任意名稱,然后轉(zhuǎn)至保存的位置,打開 ghci,執(zhí)行 :l baby.hs。這樣我們的函數(shù)就裝載成功,可以呼叫了。

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

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

doubleUs?x?y?=?x*2?+?y*2

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

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

你可以在其他函數(shù)中呼叫你編寫的函數(shù),如此一來我們可以將 doubleUs 函數(shù)改為:

doubleUs?x?y?=?doubleMe?x?+?doubleMe?y

http://wiki.jikexueyuan.com/project/haskell-guide/images/baby.png" alt="" />

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

Haskell 中的函數(shù)并沒有順序,所以先聲明 doubleUs 還是先聲明 doubleMe 都是同樣的。如下,我們編寫一個函數(shù),它將小于 100 的數(shù)都乘以 2,因為大于 100 的數(shù)都已經(jīng)足夠大了!

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

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

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

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

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

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

List 入門

http://wiki.jikexueyuan.com/project/haskell-guide/images/list.png" alt="" />

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

*Note*: 在 ghci 下,我們可以使用 ``let`` 關(guān)鍵字來定義一個常量。在 ghci 下執(zhí)行 ``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 就會報出這幾個字元不是數(shù)字的錯誤。字串實際上就是一組字元的 List,"Hello" 只是 ['h','e','l','l','o'] 的語法糖而已。所以我們可以使用處理 List 的函數(shù)來對字串進行操作。 將兩個 List 合并是很常見的操作,這可以通過 ++ 運算子實現(xiàn)。

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 上追加元素,那可得執(zhí)行好一會兒了。所以說,用 : 運算子往一個 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 中混合放置字元和數(shù)組相同,混合放置數(shù)值和字元的 List 也是同樣不可以的。當 List 內(nèi)裝有可比較的元素時,使用 >>= 可以比較 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 做啥?如下是幾個常用的函數(shù):

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 想象為一頭怪獸,那這就是它的樣子:

http://wiki.jikexueyuan.com/project/haskell-guide/images/listmonster.png" alt="" />

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

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

糟糕,程式直接跳出錯誤。如果怪獸都不存在的話,那他的頭也不會存在。在使用 headtail,lastinit 時要小心別用到空的 List 上,這個錯誤不會在編譯時被捕獲。所以說做些工作以防止從空 List 中取值會是個好的做法。

length 返回一個 List 的長度。

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

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

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

reverse 將一個 List 反轉(zhuǎn):

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 長度的元素個數(shù),只能得到原 List。若 take 0 個元素,則會得到一個空 List! droptake 的用法大體相同,它會刪除一個 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 中最大的那個元素。minimun 返回最小的。

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,通常以中綴函數(shù)的形式呼叫它。

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

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

使用 Range

http://wiki.jikexueyuan.com/project/haskell-guide/images/cowboy.png" alt="" />

今天如果想得到一個包含 1 到 20 之間所有數(shù)的 List,你會怎么做? 我們可以將它們一個一個用鍵盤打出來,但很明顯地這不是一個完美的方案,特別是你追求一個好的程式語言的時候。我們想用的是區(qū)間 (Range)。Range 是構(gòu)造 List 方法之一,而其中的值必須是可枚舉的,像 1、2、3、4...字元同樣也可以枚舉,字母表就是 A..Z 所有字元的枚舉。而名字就不可以枚舉了,"john" 后面是誰?我不知道。

要得到包含 1 到 20 中所有自然數(shù)的 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 的特點是他還允許你指定每一步該跨多遠。譬如說,今天的問題換成是要得到 1 到 20 間所有的偶數(shù)或者 3 的倍數(shù)該怎樣?

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 的冪。一方面是因為步長只能標明一次,另一方面就是僅憑前幾項,數(shù)組的后項是不能確定的。要得到 20 到 1 的 List,[20..1] 是不可以的。必須得 [20,19..1]。 在 Range 中使用浮點數(shù)要格外小心!出于定義的原因,浮點數(shù)并不精確。若是使用浮點數(shù)的話,你就會得到如下的糟糕結(jié)果

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

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

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

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

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 接受一個值作參數(shù),并返回一個僅包含該值的無限 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

http://wiki.jikexueyuan.com/project/haskell-guide/images/kermit.png" alt="" />

學過數(shù)學的你對集合的 comprehension (Set Comprehension) 概念一定不會陌生。通過它,可以從既有的集合中按照規(guī)則產(chǎn)生一個新集合。前十個偶數(shù)的 set comprehension 可以表示為http://wiki.jikexueyuan.com/project/haskell-guide/images/setnotation.png" alt="" />,豎線左端的部分是輸出函數(shù),x 是變數(shù),N 是輸入集合。在 Haskell 下,我們可以通過類似 take 10 [2,4..] 的程式碼來實現(xiàn)。但若是把簡單的乘 2 改成更復(fù)雜的函數(shù)操作該怎么辦呢?用 list comprehension,它與 set comprehension 十分的相似,用它取前十個偶數(shù)輕而易舉。這個 list comprehension 可以表示為:

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

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

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

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

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

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

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

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

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

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

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

除了多個限制條件之外,從多個 List 中取元素也是可以的。這樣的話 comprehension 會把所有的元素組合交付給我們的輸出函數(shù)。在不過濾的前提 下,取自兩個長度為 4 的集合的 comprehension 會產(chǎn)生一個長度為 16 的 List。假設(shè)有兩個 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 的結(jié)果該如何?

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 函數(shù)吧!就叫做 length'!

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

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

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

測試一下:

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

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

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

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

http://wiki.jikexueyuan.com/project/haskell-guide/images/tuple.png" alt="" />

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

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

動腦筋,在 Haskell 中表示二維向量該如何?使用 List 是一種方法,它倒也工作良好。若要將一組向量置于一個 List 中來表示平面圖形又該怎樣?我們可以寫類似 [[1,2],[8,11],[4,5]] 的程式碼來實現(xiàn)。但問題在于,[[1,2],[8,11,5],[4,5]] 也是同樣合法的,因為其中元素的型別都相同。盡管這樣并不靠譜,但編譯時并不會報錯。然而一個長度為 2 的 Tuple (也可以稱作序?qū)?,Pair) ,是一個獨立的類型,這便意味著一個包含一組序?qū)Φ?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)]

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

使用 Tuple 前應(yīng)當事先明確一條數(shù)據(jù)中應(yīng)該由多少個項。每個不同長度的 Tuple 都是獨立的型別,所以你就不可以寫個函數(shù)來給它追加元素。而唯一能做的,就是通過函數(shù)來給一個 List 追加序?qū)?,三元組或是四元組等內(nèi)容。

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

同 List 相同,只要其中的項是可比較的,Tuple 也可以比較大小,只是你不可以像比較不同長度的 List 那樣比較不同長度的 Tuple 。如下是兩個有用的序?qū)Σ僮骱瘮?shù):

fst 返回一個序?qū)Φ氖醉棥?/p>

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

snd 返回序?qū)Φ奈岔棥?/p>

ghci> snd (8,11)
11
ghci> snd ("Wow", False)
False
*Note*:這兩個函數(shù)僅對序?qū)τ行?,而不能?yīng)用于三元組,四元組和五元組之上。稍后,我們將過一遍從 Tuple 中取數(shù)據(jù)的所有方式。

有個函數(shù)很 cool,它就是 zip。它可以用來生成一組序?qū)?(Pair) 的 List。它取兩個 List,然后將它們交叉配對,形成一組序?qū)Φ?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。第一個元素配第一個,第二個元素配第二個..以此類推。注意,由于序?qū)χ锌梢院胁煌男蛣e,zip 函數(shù)可能會將不同型別的序?qū)M合在一起。若是兩個不同長度的 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")]

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

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

剛才我們是從三個 List 中取值,并且通過輸出函數(shù)將其組合為一個三元組。只要在 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]

已經(jīng)差不多了。最后修改函數(shù),告訴它只要周長為 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)]

得到正確結(jié)果!這便是函數(shù)式程式語言的一般思路:先取一個初始的集合并將其變形,執(zhí)行過濾條件,最終取得正確的結(jié)果。

上一篇:輸入與輸出下一篇:模組 (Modules)