# HASKELL 趣學指南

You can read the book online [here](https://learnyouahaskell.mno2.org/).

此書為 [LEARN YOU A HASKELL FOR GREAT GOOD](http://learnyouahaskell.com/) 的 [CC](https://creativecommons.org/licenses/by-nc-sa/3.0/) 中文翻譯。最早是由 fleuria 擺在網路上前幾章的簡體翻譯，有 MnO2 接手並把後半部分翻譯完成。之後在網路上集結眾多[貢獻者](https://github.com/MnO2/learnyouahaskell-zh/graphs/contributors)們維護的版本。

由於是借助大家熱情而無償產出的書籍，難免有所錯誤。如果有看到錯誤還請 send pull request。原本的第一版本的 gitbook 並沒有跟 github integration 自動發佈修正的錯誤，需要人力 merge 並 push 因此耽誤了不少。新的版本的 gitbook 就有 github integration，PR merge 後就能自動發佈。


# LANGS

* [繁體中文](broken://pages/-LjUmq7NdEolX3AFAZAi)
* [簡體中文](/zh-cn)


# HASKELL 趣學指南

此書為 [LEARN YOU A HASKELL FOR GREAT GOOD](http://learnyouahaskell.com/) 的 [CC](https://creativecommons.org/licenses/by-nc-sa/3.0/) 中文翻譯。最早是由 fleuria 擺在網路上前幾章的簡體翻譯，有 MnO2 接手並把後半部分翻譯完成。之後在網路上集結眾多[貢獻者](https://github.com/MnO2/learnyouahaskell-zh/graphs/contributors)們維護的版本。

由於是借助大家熱情而無償產出的書籍，難免有所錯誤。如果有看到錯誤還請 send pull request。原本的第一版本的 gitbook 並沒有跟 github integration 自動發佈修正的錯誤，需要人力 merge 並 push 因此耽誤了不少。新的版本的 gitbook 就有 github integration，PR merge 後就能自動發佈。


# SUMMARY

* [Chap 01 - Introduction](/zh-tw/ch01/introduction)
* [Chap 02 - Ready Go](/zh-tw/ch02/ready-go)
* [Chap 03 - Type And Typeclass](/zh-tw/ch03/type-and-typeclass)
* [Chap 04 - Syntax in Function](/zh-tw/ch04/syntax-in-function)
* [Chap 05 - Recursion](/zh-tw/ch05/recursion)
* [Chap 06 - High Order Function](/zh-tw/ch06/high-order-function)
* [Chap 07 - Module](/zh-tw/ch07/module)
* [Chap 08 - Build Our Own Type and Typeclass](/zh-tw/ch08/build-our-own-type-and-typeclass)
* [Chap 09 - Input and Output](/zh-tw/ch09/input-and-output)
* [Chap 10 - Functionally Solving Problems](/zh-tw/ch10/functionally-solving-problems)
* [Chap 11 - Functors, Applicative Functors 與 Monoids](/zh-tw/ch11/functors-applicative-functors-and-monoids)
* [Chap 12 - A Fistful of Monad](/zh-tw/ch12/a-fistful-of-monads)
* [Chap 13 - For a Few Monad More](/zh-tw/ch13/for-a-few-monads-more)
* [Chap 14 - Zippers](/zh-tw/ch14/zippers)
* [FAQ](/zh-tw/faq/faq)
* [Resource](/zh-tw/resource-1)


# ch01


# 簡介

## 關於這份教學

歡迎來到 *Haskell 趣學指南*！會想看這篇文章表示你對學習 Haskell 有很大的興趣。你來對地方了，來讓我簡單介紹一下這個教學。

撰寫這份教學，一方面是讓我自己對 Haskell 更熟練，另一方面是希望能夠分享我的學習經驗，幫助初學者更快進入狀況。網路上已經有無數 Haskell 的教學文件，在我學習的過程中，我並不限於只參考一份來源。我常常閱讀不同的教學文章，他們每個都從不同的角度出發。參考這些資源讓我能將知識化整為零。這份教學是希望提供更多的機會能讓你找到你想要得到的解答。

![](/files/-LjUo1dhftzl0-MgH4Ty)

這份教學主要針對已經有使用命令式程式語言 (imperative programming languages) 寫程式經驗 (C, C++, Java, Python …) 、卻未曾接觸過函數式程式語言 (functional programming languages) (Haskell, ML, OCaml …) 的讀者。就算沒有寫程式經驗也沒關係，會想學 Haskell 的人我相信都是很聰明的。

若在學習中遇到什麼地方不懂的，Freenode IRC 上的 #Haskell 頻道是提問的絕佳去處。那裡的人都很友善，有耐心且能體諒初學者。 (譯註： Stackoverflow 上的 #haskell tag 也有很多 Haskell 神人們耐心地回答問題，提供給不習慣用 IRC 的人的另一個選擇。)

我經歷了不少挫折才學會 Haskell，在初學的時候它看起來是如此奇怪的語言。但有一天我突然開竅了，之後的學習便如魚得水。我想要表達的是：儘管 Haskell 乍看下如此地詭異，但假如你對程式設計十分有興趣，他非常值得你學習。學習 Haskell 讓你想起你第一次寫程式的感覺。非常有趣，而且強迫你 Think different。

## 什麼是 Haskell？

![](/files/-LjUo1dj3QPAZRkB_T3V)

Haskell 與其他語言不同，是一門*純粹函數式程式語言 (purely functional programming language)*。在一般常見的命令式語言中，要執行操作的話是給電腦一組命令，而狀態會隨著命令的執行而改變。例如你指派變數 `a` 的值為 5，而隨後做了其它一些事情之後 a 就可能變成的其它值。有控制流程 (control flow)，你就可以重複執行操作。然而在純粹函數式程式語言中，你不是像命令式語言那樣命令電腦「要做什麼」，而是通過用函數來描述出問題「是什麼」，如「*階乘是指從1到某個數的乘積」，「一個串列中數字的和」是指把第一個數字跟剩餘數字的和相加。你用宣告函數是什麼的形式來寫程式。另外，變數 (variable) 一旦被指定，就不可以更改了，你已經說了 `a` 就是 5，就不能再另說 a 是別的什麼數。（譯註：其實用 variable 來表達造成字義的 overloading，會讓人聯想到 imperative languages 中 variable 是代表狀態，但在 functional languages 中 variable 是相近於數學中使用的 variable。`x=5` 代表 `x` 就是 5，不是說 `x` 在 5 這個狀態。) 所以說，在純粹函數式程式語言中的函數能做的唯一事情就是利用引數計算結果，不會產生所謂的"副作用 (side effect)" (譯註：也就是改變非函數內部的狀態，像是 imperative languages 裡面動到 global variable 就是 side effect)。一開始會覺得這限制很大，不過這也是他的優點所在：若以同樣的參數呼叫同一個函數兩次，得到的結果一定是相同。這被稱作“*&#x5F15;用透明 (Referential Transparency)\_” (譯註：這就跟數學上函數的使用一樣)。如此一來編譯器就可以理解程式的行為，你也很容易就能驗證一個函數的正確性，繼而可以將一些簡單的函數組合成更複雜的函數。

![](/files/-LjUo1dlbx2FAos_Bi26)

Haskell 是*惰性 (lazy)* 的。也就是說若非特殊指明，函數在真正需要結果以前不會被求值。再加上引用透明，你就可以把程式僅看作是數據的一系列變形。如此一來就有了很多有趣的特性，如無限長度的資料結構。假設你有一個 List: `xs = [1,2,3,4,5,6,7,8]`，還有一個函數 `doubleMe`，它可以將一個 List 中的所有元素都乘以二，返回一個新的 List。若是在命令式語言中，把一個 List 乘以 8，執行 `doubleMe(doubleMe(doubleMe(xs)))`，得遍歷三遍 `xs` 才會得到結果。而在惰性語言中，調用 `doubleMe` 時並不會立即求值，它會說“嗯嗯，待會兒再做！”。不過一旦要看結果，第一個 `doubleMe` 就會對第二個說“給我結果，快！”第二個 `doubleMe` 就會把同樣的話傳給第三個 `doubleMe`，第三個 `doubleMe` 只能將 1 乘以 2 得 2 後交給第二個，第二個再乘以 2 得 4 交給第一個，最終得到第一個元素 8。也就是說，這一切只需要遍歷一次 list 即可，而且僅在你真正需要結果時才會執行。惰性語言中的計算只是一組初始數據和變換公式。

![](/files/-LjUo1dnBCTelT2ba6JA)

Haskell 是*靜態類型 (statically typed)* 的。當你編譯程式時，編譯器需要明確哪個是數字，哪個是字串。這就意味着很大一部分錯誤都可以在編譯時被發現，若試圖將一個數字和字串相加，編譯器就會報錯。Haskell 擁有一套強大的類型系統，支持自動類型推導 (type inference)。這一來你就不需要在每段程式碼上都標明它的類型，像計算 `a=5+4`，你就不需另告訴編譯器“ a 是一個數值”，它可以自己推導出來。類型推導可以讓你的程式更加簡練。假設有個函數是將兩個數值相加，你不需要聲明其類型，這個函數可以對一切可以相加的值進行計算。

Haskell 採納了很多高階程式語言的概念，因而它的程式碼*優雅且簡練*。與同層次的命令式語言相比，Haskell 的程式碼往往會更短，更短就意味着更容易理解，bug 也就更少。

Haskell 這語言是一群非常聰明的人設計的 (他們每個人都有 PhD 學位)。最初的工作始於 1987 年，一群學者聚在一起想設計一個屌到爆的程式語言。到了 2003 年，他們公開了 Haskell Report，這份報告描述了 Haskell 語言的一個穩定版本。(譯註：這份報告是 Haskell 98 標準的修訂版，Haskell 98 是在 1999 年公開的，是目前 Haskell 各個編譯器實現預設支援的標準。在 2010 年又公開了另一份 Haskell 2010 標準，詳情可見穆信成老師所撰寫的[簡介](http://www.iis.sinica.edu.tw/~scm/ncs/2010/07/haskell-2010-report/)。

## 要使用 Haskell 有哪些要求呢？

一句話版本的答案是：你只需要一個編輯器和一個編譯器。在這裡我們不會對編輯器多加著墨，你可以用任何你喜歡的編輯器。至於編譯器，在這份教學中我們會使用目前最流行的版本：GHC。而安裝 GHC 最方便的方法就是去下載 Haskell Platform，他包含了許多現成 Runtime Library 讓你方便寫程式。(譯註：Ubuntu 的使用者有現成的套件可以使用，可以直接 `apt-get install Haskell-platform` 來安裝。但套件的版本有可能比較舊。)

GHC 可以解釋執行 Haskell Script （通常是以 `.hs` 作為結尾），也可以編譯。它還有個互動模式，你可以在裡面呼叫 Script 裡定義的函數，即時得到結果。 對於學習而言，這比每次修改都編譯執行要方便的多。想進入互動模式，只要打開控制台輸入 `ghci` 即可。假設你在 `myfunctions.hs` 裡定義了一些函數，在 ghci 中輸入 `:l myfunctions.hs`，ghci 便會載入 `myfunctions.hs`。之後你便可以呼叫你定義的函數。一旦修改了這個 `.hs` 檔案的內容，再次執行 `:l myfunctions.hs` 或者相同作用的 `:r` ，都可以重新載入該檔案。我自己通常就是在 `.hs` 檔案中定義幾個函數，再到 ghci 載入，呼叫看看，再修改再重新載入。這也正是我們往後的基本流程。


# ch02


# 從零開始

## 準備好了嗎？

![](/files/-MSkgk5o_Y3FPOxazup5)

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

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

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

首先來看一些簡單的運算

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

很簡單吧！你也可以在一行中使用多個運算子，他們會按照運算子優先順序執行計算，而使用括號可以改變執行的優先順序。

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

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

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

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

相等性可以這樣判定

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

那執行 `5+"llama"` 或者 `5==True` 會怎樣？ 如果我們真的試著在 ghci 中跑，會得到下列的錯誤訊息：

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

![](/files/-LjUmroua91hJz0uZy6a)

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

```haskell
ghci> succ 8
9
```

`succ` 函數返回一個數的後繼 (successor)。而且如你所見，在 Haskell 中是用空格來將函數與參數分隔的。至於呼叫多個參數的函數也很容易，`min` 和 `max` 接受兩個可比較大小的參數，並返回較大或者較小的那個數。

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

函數呼叫擁有最高的優先順序，如下兩句是等效的

```haskell
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。

```haskell
doubleMe x = x + x
```

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

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

```haskell
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
```

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

```haskell
doubleUs x y = x*2 + y*2
```

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

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

你可以在其他函數中呼叫你編寫的函數，如此一來我們可以將 `doubleUs` 函數改為：

```haskell
doubleUs x y = doubleMe x + doubleMe y
```

![](/files/-MSkgk5uOvwZca9P4C8r)

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

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

```haskell
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，可以如此修改：

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

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

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

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

## List 入門

![](/files/-LjUmrrLvTlHXk9Qpq_4)

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

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

```haskell
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 合併是很常見的操作，這可以通過 `++` 運算子實現。

```haskell
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 前端插入元素會是更好的選擇。

```haskell
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。

```haskell
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：

```haskell
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 的大小。它會先比較第一個元素，若它們的值相等，則比較下一個，以此類推。

```haskell
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 的首個元素。

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

**tail** 返回一個 List 的尾部，也就是 List 除去頭部之後的部分。

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

**last** 返回一個 List 的最後一個元素。

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

**init** 返回一個 List 除去最後一個元素的部分。

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

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

![](/files/-LjUmrp1p0szVvm8I8tM)

試一下，若是取一個空 List 的 `head` 又會怎樣？

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

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

**length** 返回一個 List 的長度。

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

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

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

**reverse** 將一個 List 反轉:

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

**take** 返回一個 List 的前幾個元素，看：

```haskell
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 中的前幾個元素。

```haskell
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` 返回最小的。

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

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

```haskell
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，通常以中綴函數的形式呼叫它。

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

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

## 使用 Range

![](/files/-LjUmrp44ziswRiPswqz)

今天如果想得到一個包含 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 那就鐵定是個笨方法。

```haskell
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 間所有的偶數或者 3 的倍數該怎樣？

```haskell
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 中使用浮點數要格外小心！出於定義的原因，浮點數並不精確。若是使用浮點數的話，你就會得到如下的糟糕結果

```haskell
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 。如果你只是想看一下它的運算結果而已，它會運行個沒完的。所以應該在某處劃好範圍。

```haskell
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 差不多。

```haskell
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

![](/files/-MSkgk668zzyWJlAJc2q)

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

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

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

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

cool，靈了。若是取 50 到 100 間所有除7的餘數為 3 的元素該怎麼辦？簡單：

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

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

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

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

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

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

```haskell
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]`， 要取它們所有組合的積，可以這樣：

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

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

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

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

```haskell
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'`!

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

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

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

測試一下：

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

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

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

```haskell
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

![](/files/-MSkgk6CqM_vGLWjNmgB)

從某種意義上講，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)]`，就會報出以下的錯誤：

```haskell
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** 返回一個序對的首項。

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

**snd** 返回序對的尾項。

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

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

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

```haskell
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 會怎麼樣？

```haskell
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 也是可以的：

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

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

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

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

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

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

```haskell
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)]
```

得到正確結果！這便是函數式程式語言的一般思路：先取一個初始的集合併將其變形，執行過濾條件，最終取得正確的結果。


# ch03


# Types and Typeclasses

## Type

![](/files/-MSkglyBL1-z1F0RtJED)

之前我們有說過 Haskell 是 Static Type，這表示在編譯時期每個表達式的型別都已經確定下來，這提高了程式碼的安全性。若程式碼中有讓布林值與數字相除的動作，就不會通過編譯。這樣的好處就是與其讓程序在運行時崩潰，不如在編譯時就找出可能的錯誤。Haskell 中所有東西都有型別，因此在編譯的時候編譯器可以做到很多事情。

與 Java 和 Pascal 不同，Haskell 支持型別推導。寫下一個數字，你就沒必要另告訴 Haskell 說"它是個數字"，它自己能推導出來。這樣我們就不必在每個函數或表達式上都標明其型別了。在前面我們只簡單涉及一下 Haskell 的型別方面的知識，但是理解這一型別系統對於 Haskell 的學習是至關重要的。

型別是每個表達式都有的某種標籤，它標明了這一表達式所屬的範疇。例如，表達式 `True` 是 `boolean` 型，`"hello"`是個字串，等等。

可以使用 ghci 來檢測表達式的型別。使用 `:t` 命令後跟任何可用的表達式，即可得到該表達式的型別，先試一下：

```haskell
ghci> :t 'a'  
'a' :: Char  
ghci> :t True  
True :: Bool  
ghci> :t "HELLO!"  
"HELLO!" :: [Char]  
ghci> :t (True, 'a')  
(True, 'a') :: (Bool, Char)  
ghci> :t 4 == 5  
4 == 5 :: Bool
```

![](/files/-LjUmrnlEUe_26BP4s5_)

可以看出，`:t` 命令處理一個表達式的輸出結果為表達式後跟 `::` 及其型別，`::` 讀作"它的型別為"。凡是明確的型別，其首字母必為大寫。`'a'`, 如它的樣子，是 `Char` 型別，易知是個字元 (character)。`True` 是 `Bool` 型別，也靠譜。不過這又是啥，檢測 `"hello"` 得一個 `[Char]` 這方括號表示一個 List，所以我們可以將其讀作"一組字元的 List"。而與 List 不同，每個 Tuple 都是獨立的型別，於是 `(True,'a')` 的型別是 `(Bool,Char)`，而 `('a','b','c')` 的型別為 `(Char,Char,Char)`。`4==5` 一定回傳 `False`，所以它的型別為 Bool。

同樣，函數也有型別。編寫函數時，給它一個明確的型別聲明是個好習慣，比較短的函數就不用多此一舉了。還記得前面那個過濾大寫字母的 List Comprehension 嗎？給它加上型別聲明便是這個樣子：

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

`removeNonUppercase` 的型別為 `[Char]->[Char]`，從它的參數和回傳值的型別上可以看出，它將一個字串映射為另一個字串。`[Char]` 與 `String` 是等價的，但使用 `String` 會更清晰：`removeNonUppercase :: String -> String`。編譯器會自動檢測出它的型別，我們還是標明了它的型別聲明。要是多個參數的函數該怎樣？如下便是一個將三個整數相加的簡單函數。

```haskell
addThree :: Int -> Int -> Int -> Int  
addThree x y z = x + y + z
```

參數之間由 `->` 分隔，而與回傳值之間並無特殊差異。回傳值是最後一項，參數就是前三項。稍後，我們將講解為何只用 `->` 而不是 `Int,Int,Int->Int` 之類"更好看"的方式來分隔參數。

如果你打算給你編寫的函數加上個型別聲明卻拿不準它的型別是啥，只要先不寫型別聲明，把函數體寫出來，再使用 `:t` 命令測一下即可。函數也是表達式，所以 `:t` 對函數也是同樣可用的。

如下是幾個常見的型別：

**Int** 表示整數。7 可以是 Int，但 7.2 不可以。Int 是有界的，也就是說它由上限和下限。對 32 位的機器而言，上限一般是 `2147483647`，下限是 `-2147483648`。

**Integer** 表示...厄...也是整數，但它是無界的。這就意味着可以用它存放非常非常大的數，我是說非常大。它的效率不如 Int 高。

```haskell
factorial :: Integer -> Integer  
factorial n = product [1..n]
```

```haskell
ghci> factorial 50  
30414093201713378043612608166064768844377641568960512000000000000
```

**Float** 表示單精度的浮點數。

```haskell
circumference :: Float -> Float  
circumference r = 2 * pi * r
```

```haskell
ghci> circumference 4.0  
25.132742
```

**Double** 表示雙精度的浮點數。

```haskell
circumference' :: Double -> Double  
circumference' r = 2 * pi * r
```

```haskell
ghci> circumference' 4.0  
25.132741228718345
```

**Bool** 表示布林值，它只有兩種值：`True` 和 `False`。

**Char** 表示一個字元。一個字元由單引號括起，一組字元的 List 即為字串。

Tuple 的型別取決於它的長度及其中項的型別。注意，空 Tuple 同樣也是個型別，它只有一種值：`()`。

## Type variables

你覺得 `head` 函數的型別是啥？它可以取任意型別的 List 的首項，是怎麼做到的呢？我們查一下！

```haskell
ghci> :t head  
head :: [a] -> a
```

![](/files/-MSkglyG3qyEVyAXOD5i)

嗯! `a` 是啥？型別嗎？想想我們在前面說過，凡是型別其首字母必大寫，所以它不會是個型別。它是個型別變數，意味着 a 可以是任意的型別。這一點與其他語言中的泛型 (generic) 很相似，但在 Haskell 中要更為強大。它可以讓我們輕而易舉地寫出型別無關的函數。使用到型別變數的函數被稱作"多態函數 "，`head` 函數的型別聲明裡標明了它可以取任意型別的 List 並回傳其中的第一個元素。

在命名上，型別變數使用多個字元是合法的，不過約定俗成，通常都是使用單個字元，如 `a`, `b` ,`c` ,`d`...

還記得 `fst`？我們查一下它的型別：

```haskell
ghci> :t fst  
fst :: (a, b) -> a
```

可以看到`fst`取一個包含兩個型別的 Tuple 作參數，並以第一個項的型別作為回傳值。這便是 `fst` 可以處理一個含有兩種型別項的 pair 的原因。注意，`a` 和 `b` 是不同的型別變數，但它們不一定非得是不同的型別，它只是標明了首項的型別與回傳值的型別相同。

## Typeclasses入門

![](/files/-MSkglyID4CHaTUIj87G)

型別定義行為的介面，如果一個型別屬於某 Typeclass，那它必實現了該 Typeclass 所描述的行為。很多從 OOP 走過來的人們往往會把 Typeclass 當成物件導向語言中的 `class` 而感到疑惑，厄，它們不是一回事。易於理解起見，你可以把它看做是 Java 的 interface。

`==` 函數的型別聲明是怎樣的？

```haskell
ghci> :t (==)  
(==) :: (Eq a) => a -> a -> Bool
```

```
*Note*: 判斷相等的==運算子是函數，``+-*/``之類的運算子也是同樣。在預設條件下，它們多為中綴函數。若要檢查它的型別，就必須得用括號括起使之作為另一個函數，或者說以首碼函數的形式呼叫它。
```

有意思。在這裡我們見到個新東西：`=>` 符號。它左邊的部分叫做型別約束。我們可以這樣閲讀這段型別聲明："相等函數取兩個相同型別的值作為參數並回傳一個布林值，而這兩個參數的型別同在 Eq 類之中(即型別約束)"

**Eq** 這一 Typeclass 提供了判斷相等性的介面，凡是可比較相等性的型別必屬於 `Eq` class。

```haskell
ghci> 5 == 5   
True   
ghci> 5 /= 5   
False   
ghci> 'a' == 'a'   
True   
ghci> "Ho Ho" == "Ho Ho"   
True   
ghci> 3.432 == 3.432   
True
```

`elem` 函數的型別為: `(Eq a)=>a->[a]->Bool`。這是它在檢測值是否存在於一個 List 時使用到了==的緣故。

幾個基本的 Typeclass：

**Eq** 包含可判斷相等性的型別。提供實現的函數是 `==` 和 `/=`。所以，只要一個函數有Eq類的型別限制，那麼它就必定在定義中用到了 `==` 和 `/=`。剛纔說了，除函數以外的所有型別都屬於 `Eq`，所以它們都可以判斷相等性。

**Ord** 包含可比較大小的型別。除了函數以外，我們目前所談到的所有型別都屬於 `Ord` 類。`Ord` 包中包含了`<, >, <=, >=` 之類用於比較大小的函數。`compare` 函數取兩個 `Ord` 類中的相同型別的值作參數，回傳比較的結果。這個結果是如下三種型別之一：`GT, LT, EQ`。

```haskell
ghci> :t (>)  
(>) :: (Ord a) => a -> a -> Bool
```

型別若要成為Ord的成員，必先加入Eq家族。

```haskell
ghci> "Abrakadabra" < "Zebra"  
True  
ghci> "Abrakadabra" `compare` "Zebra"  
LT  
ghci> 5 >= 2  
True  
ghci> 5 `compare` 3  
GT
```

**Show** 的成員為可用字串表示的型別。目前為止，除函數以外的所有型別都是 `Show` 的成員。操作 Show Typeclass，最常用的函數表示 `show`。它可以取任一Show的成員型別並將其轉為字串。

```haskell
ghci> show 3  
"3"  
ghci> show 5.334  
"5.334"  
ghci> show True  
"True"
```

**Read** 是與 `Show` 相反的 Typeclass。`read` 函數可以將一個字串轉為 `Read` 的某成員型別。

```haskell
ghci> read "True" || False  
True  
ghci> read "8.2" + 3.8  
12.0  
ghci> read "5" - 2  
3  
ghci> read "[1,2,3,4]" ++ [3]  
[1,2,3,4,3]
```

一切良好，如上的所有型別都屬於這一 Typeclass。嘗試 `read "4"` 又會怎樣？

```haskell
ghci> read "4"  
< interactive >:1:0:  
    Ambiguous type variable `a' in the constraint:  
      `Read a' arising from a use of `read' at <interactive>:1:0-7  
    Probable fix: add a type signature that fixes these type variable(s)
```

ghci 跟我們說它搞不清楚我們想要的是什麼樣的回傳值。注意呼叫 `read` 後跟的那部分，ghci 通過它來辨認其型別。若要一個 `boolean` 值，他就知道必須得回傳一個 `Bool` 型別的值。但在這裡它只知道我們要的型別屬於 Read Typeclass，而不能明確到底是哪個。看一下 `read` 函數的型別聲明吧：

```haskell
ghci> :t read  
read :: (Read a) => String -> a
```

看，它的回傳值屬於 ReadTypeclass，但我們若用不到這個值，它就永遠都不會得知該表達式的型別。所以我們需要在一個表達式後跟`::` 的*型別註釋*，以明確其型別。如下：

```haskell
ghci> read "5" :: Int  
5  
ghci> read "5" :: Float  
5.0  
ghci> (read "5" :: Float) * 4  
20.0  
ghci> read "[1,2,3,4]" :: [Int]  
[1,2,3,4]  
ghci> read "(3, 'a')" :: (Int, Char)  
(3, 'a')
```

編譯器可以辨認出大部分表達式的型別，但遇到 `read "5"` 的時候它就搞不清楚究竟該是 Int 還是 Float 了。只有經過運算，Haskell 才會明確其型別；同時由於 Haskell 是靜態的，它還必須得在 編譯前搞清楚所有值的型別。所以我們就最好提前給它打聲招呼："嘿，這個表達式應該是這個型別，省得你認不出來！"

**Enum** 的成員都是連續的型別 -- 也就是可枚舉。`Enum` 類存在的主要好處就在於我們可以在 `Range` 中用到它的成員型別：每個值都有後繼子 (successer) 和前置子 (predecesor)，分別可以通過 `succ` 函數和 `pred` 函數得到。該 Typeclass 包含的型別有：`()`, `Bool`, `Char`, `Ordering`, `Int`, `Integer`, `Float` 和 `Double`。

```haskell
ghci> ['a'..'e']  
"abcde"  
ghci> [LT .. GT]  
[LT,EQ,GT]  
ghci> [3 .. 5]  
[3,4,5]  
ghci> succ 'B'  
'C'
```

**Bounded** 的成員都有一個上限和下限。

```haskell
ghci> minBound :: Int  
-2147483648  
ghci> maxBound :: Char  
'\1114111'  
ghci> maxBound :: Bool  
True  
ghci> minBound :: Bool  
False
```

`minBound` 和 `maxBound` 函數很有趣，它們的型別都是 `(Bounded a) => a`。可以說，它們都是多態常量。

如果其中的項都屬於 `Bounded` Typeclass，那麼該 Tuple 也屬於 `Bounded`

```haskell
ghci> maxBound :: (Bool, Int, Char)  
(True,2147483647,'\1114111')
```

**Num** 是表示數字的 Typeclass，它的成員型別都具有數字的特徵。檢查一個數字的型別：

```haskell
ghci> :t 20  
20 :: (Num t) => t
```

看樣子所有的數字都是多態常量，它可以作為所有 `Num` Typeclass中的成員型別。以上便是 `Num` Typeclass 中包含的所有型別，檢測 `*` 運算子的型別，可以發現它可以處理一切的數字：

```haskell
ghci> :t (*)  
(*) :: (Num a) => a -> a -> a
```

它只取兩個相同型別的參數。所以 `(5 :: Int) * (6 :: Integer)` 會引發一個型別錯誤，而 `5 * (6 :: Integer)` 就不會有問題。

型別只有親近 `Show` 和 `Eq`，才可以加入 `Num`。

**Integral** 同樣是表示數字的 Typeclass。`Num` 包含所有的數字：實數和整數。而 Integral 僅包含整數，其中的成員型別有 `Int` 和 `Integer`。

**Floating** 僅包含浮點型別：`Float` 和 `Double`。

有個函數在處理數字時會非常有用，它便是 **fromIntegral**。其型別聲明為： `fromIntegral :: (Num b, Integral a) => a -> b`。從中可以看出，它取一個整數做參數並回傳一個更加通用的數字，這在同時處理整數和浮點時會尤為有用。舉例來說，`length` 函數的型別聲明為：`length :: [a] -> Int`，而非更通用的形式，如 `length :: (Num b) => [a] -> b`。這應該是歷史原因吧，反正我覺得挺蠢。如果取了一個 List 長度的值再給它加 3.2 就會報錯，因為這是將浮點數和整數相加。面對這種情況，我們就用 `fromIntegral (length [1,2,3,4]) + 3.2` 來解決。

注意到，`fromIntegral` 的型別聲明中用到了多個型別約束。如你所見，只要將多個型別約束放到括號裡用逗號隔開即可。


# ch04


# 函數的語法

## 模式匹配 (Pattern matching)

![](/files/-LjUmrYwjgTI3yGCEwBo)

本章講的就是 Haskell 那套獨特的語法結構，先從模式匹配開始。模式匹配通過檢查數據的特定結構來檢查其是否匹配，並按模式從中取得數據。

在定義函數時，你可以為不同的模式分別定義函數本身，這就讓程式碼更加簡潔易讀。你可以匹配一切數據型別 --- 數字，字元，List，元組，等等。我們弄個簡單函數，讓它檢查我們傳給它的數字是不是 7。

```haskell
lucky :: (Integral a) => a -> String  
lucky 7 = "LUCKY NUMBER SEVEN!"  
lucky x = "Sorry, you're out of luck, pal!"
```

在呼叫 `lucky` 時，模式會從上至下進行檢查，一旦有匹配，那對應的函數體就被應用了。這個模式中的唯一匹配是參數為 7，如果不是 7，就轉到下一個模式，它匹配一切數值並將其綁定為 `x` 。這個函數完全可以使用 `if` 實現，不過我們若要個分辨 1 到 5 中的數字，而無視其它數的函數該怎麼辦？要是沒有模式匹配的話，那可得好大一棵 `if-else` 樹了！

```haskell
sayMe :: (Integral a) => a -> String  
sayMe 1 = "One!"  
sayMe 2 = "Two!"  
sayMe 3 = "Three!"  
sayMe 4 = "Four!"  
sayMe 5 = "Five!"  
sayMe x = "Not between 1 and 5"
```

注意下，如果我們把最後匹配一切的那個模式挪到最前，它的結果就全都是 `"Not between 1 and 5"` 了。因為它自己匹配了一切數字，不給後面的模式留機會。

記得前面實現的那個階乘函數麼？當時是把 `n` 的階乘定義成了 `product [1..n]`。也可以寫出像數學那樣的遞迴實現，先說明 0 的階乘是 1 ，再說明每個正整數的階乘都是這個數與它前驅 (predecessor) 對應的階乘的積。如下便是翻譯到 Haskell 的樣子：

```haskell
factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1)
```

這就是我們定義的第一個遞迴函數。遞迴在 Haskell 中十分重要，我們會在後面深入理解。如果拿一個數(如 3)呼叫 `factorial` 函數，這就是接下來的計算步驟：先計算 `3*factorial 2`，`factorial 2` 等於 `2*factorial 1`，也就是 `3*(2*(factorial 1))`。`factorial 1` 等於 `1*factorial 0`，好，得 `3*(2*(1*factorial 0))`，遞迴在這裡到頭了，嗯 --- 我們在萬能匹配前面有定義，0 的階乘是 1。於是最終的結果等於 `3*(2*(1*1))`。若是把第二個模式放在前面，它就會捕獲包括 0 在內的一切數字，這一來我們的計算就永遠都不會停止了。這便是為什麼說模式的順序是如此重要：它總是優先匹配最符合的那個，最後才是那個萬能的。

模式匹配也會失敗。假如這個函數：

```haskell
charName :: Char -> String  
charName 'a' = "Albert"  
charName 'b' = "Broseph"  
charName 'c' = "Cecil"
```

拿個它沒有考慮到的字元去呼叫它，你就會看到這個：

```haskell
ghci> charName 'a'  
"Albert"  
ghci> charName 'b'  
"Broseph"  
ghci> charName 'h'  
"*** Exception: tut.hs:(53,0)-(55,21): Non-exhaustive patterns in function charName
```

它告訴我們說，這個模式不夠全面。因此，在定義模式時，一定要留一個萬能匹配的模式，這樣我們的程序就不會為了不可預料的輸入而崩潰了。

對 Tuple 同樣可以使用模式匹配。寫個函數，將二維空間中的向量相加該如何？將它們的 `x` 項和 `y` 項分別相加就是了。如果不瞭解模式匹配，我們很可能會寫出這樣的程式碼：

```haskell
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors a b = (fst a + fst b, snd a + snd b)
```

嗯，可以運行。但有更好的方法，上模式匹配：

```haskell
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
```

there we go！好多了！注意，它已經是個萬能的匹配了。兩個 `addVector` 的型別都是 `addVectors:: (Num a) => (a,a) -> (a,a) -> (a,a)`，我們就能夠保證，兩個參數都是序對 (Pair) 了。

`fst` 和 `snd` 可以從序對中取出元素。三元組 (Tripple) 呢？嗯，沒現成的函數，得自己動手：

```haskell
first :: (a, b, c) -> a  
first (x, _, _) = x  

second :: (a, b, c) -> b  
second (_, y, _) = y  

third :: (a, b, c) -> c  
third (_, _, z) = z
```

這裡的 `_` 就和 List Comprehension 中一樣。表示我們不關心這部分的具體內容。

說到 List Comprehension，我想起來在 List Comprehension 中也能用模式匹配：

```haskell
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]  
ghci> [a+b | (a,b) <- xs]  
[4,7,6,8,11,4]
```

一旦模式匹配失敗，它就簡單挪到下個元素。

對 List 本身也可以使用模式匹配。你可以用 `[]` 或 `:` 來匹配它。因為 `[1,2,3]` 本質就是 `1:2:3:[]` 的語法糖。你也可以使用前一種形式，像 `x:xs` 這樣的模式可以將 List 的頭部綁定為 `x`，尾部綁定為 `xs`。如果這 List 只有一個元素，那麼 `xs` 就是一個空 List。

```
*Note*：``x:xs`` 這模式的應用非常廣泛，尤其是遞迴函數。不過它只能匹配長度大於等於 1 的 List。
```

如果你要把 List 的前三個元素都綁定到變數中，可以使用類似 `x:y:z:xs` 這樣的形式。它只能匹配長度大於等於 3 的 List。

我們已經知道了對 List 做模式匹配的方法，就實現個我們自己的 `head` 函數。

```haskell
head' :: [a] -> a  
head' [] = error "Can't call head on an empty list, dummy!"  
head' (x:_) = x
```

看看管不管用：

```haskell
ghci> head' [4,5,6]  
4  
ghci> head' "Hello"  
'H'
```

漂亮！注意下，你若要綁定多個變數(用 `_` 也是如此)，我們必須用括號將其括起。同時注意下我們用的這個 `error` 函數，它可以生成一個運行時錯誤，用參數中的字串表示對錯誤的描述。它會直接導致程序崩潰，因此應謹慎使用。可是對一個空 List 取 `head` 真的不靠譜哇。

弄個簡單函數，讓它用非標準的英語給我們展示 List 的前幾項。

```haskell
tell :: (Show a) => [a] -> String  
tell [] = "The list is empty"  
tell (x:[]) = "The list has one element: " ++ show x  
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y  
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
```

這個函數顧及了空 List，單元素 List，雙元素 List 以及較長的 List，所以這個函數很安全。`(x:[])` 與 `(x:y:[])` 也可以寫作 `[x]` 和 `[x,y]` (有了語法糖，我們不必多加括號)。不過 `(x:y:_)` 這樣的模式就不行了，因為它匹配的 List 長度不固定。

我們曾用 List Comprehension 實現過自己的 `length` 函數，現在用模式匹配和遞迴重新實現它：

```haskell
length' :: (Num b) => [a] -> b  
length' [] = 0  
length' (_:xs) = 1 + length' xs
```

這與先前寫的那個 `factorial` 函數很相似。先定義好未知輸入的結果 --- 空 List，這也叫作邊界條件。再在第二個模式中將這 List 分割為頭部和尾部。說，List 的長度就是其尾部的長度加 1。匹配頭部用的 `_`，因為我們並不關心它的值。同時也應明確，我們顧及了 List 所有可能的模式：第一個模式匹配空 List，第二個匹配任意的非空 List。

看下拿 `"ham"` 呼叫 `length'` 會怎樣。首先它會檢查它是否為空 List。顯然不是，於是進入下一模式。它匹配了第二個模式，把它分割為頭部和尾部並無視掉頭部的值，得長度就是 `1+length' "am"`。ok。以此類推，`"am"` 的 `length` 就是 `1+length' "m"`。好，現在我們有了 `1+(1+length' "m")`。`length' "m"` 即 `1+length ""` (也就是 `1+length' []` )。根據定義，`length' []` 等於 `0`。最後得 `1+(1+(1+0))`。

再實現 `sum`。我們知道空 List 的和是 0，就把它定義為一個模式。我們也知道一個 List 的和就是頭部加上尾部的和的和。寫下來就成了：

```haskell
sum' :: (Num a) => [a] -> a  
sum' [] = 0  
sum' (x:xs) = x + sum' xs
```

還有個東西叫做 `as` 模式，就是將一個名字和 `@` 置於模式前，可以在按模式分割什麼東西時仍保留對其整體的引用。如這個模式 `xs@(x:y:ys)`，它會匹配出與 `x:y:ys` 對應的東西，同時你也可以方便地通過 `xs` 得到整個 List，而不必在函數體中重複 `x:y:ys`。看下這個 quick and dirty 的例子：

```haskell
capital :: String -> String  
capital "" = "Empty string, whoops!"  
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
```

```haskell
ghci> capital "Dracula"  
"The first letter of Dracula is D"
```

我們使用 `as` 模式通常就是為了在較大的模式中保留對整體的引用，從而減少重複性的工作。

還有——你不可以在模式匹配中使用 `++`。若有個模式是 `(xs++ys)`，那麼這個 List 該從什麼地方分開呢？不靠譜吧。而 `(xs++[x,y,z])` 或只一個 `(xs++[x])` 或許還能說的過去，不過出於 List 的本質，這樣寫也是不可以的。

## 什麼是 Guards

模式用來檢查一個值是否合適並從中取值，而 guard 則用來檢查一個值的某項屬性是否為真。咋一聽有點像是 `if` 語句，實際上也正是如此。不過處理多個條件分支時 guard 的可讀性要高些，並且與模式匹配契合的很好。

![](/files/-LjUmrZ4ZYBBw2RpQOpu)

在講解它的語法前，我們先看一個用到 guard 的函數。它會依據你的 BMI 值 (body mass index，身體質量指數)來不同程度地侮辱你。BMI 值即為體重除以身高的平方。如果小於 18.5，就是太瘦；如果在 18.5 到 25 之間，就是正常；25 到 30 之間，超重；如果超過 30，肥胖。這就是那個函數(我們目前暫不為您計算 BMI，它只是直接取一個 BMI 值)。

```haskell
bmiTell :: (RealFloat a) => a -> String  
bmiTell bmi  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"
```

guard 由跟在函數名及參數後面的豎綫標誌，通常他們都是靠右一個縮進排成一列。一個 guard 就是一個布爾表達式，如果為真，就使用其對應的函數體。如果為假，就送去見下一個 guard，如之繼續。如果我們用 24.3 呼叫這個函數，它就會先檢查它是否小於等於 18.5，顯然不是，於是見下一個 guard。24.3 小於 25.0，因此通過了第二個 guard 的檢查，就返回第二個字串。

在這裡則是相當的簡潔，不過不難想象這在命令式語言中又會是怎樣的一棵 if-else 樹。由於 if-else 的大樹比較雜亂，若是出現問題會很難發現，guard 對此則十分清楚。

最後的那個 guard 往往都是 `otherwise`，它的定義就是簡單一個 `otherwise = True` ，捕獲一切。這與模式很相像，只是模式檢查的是匹配，而它們檢查的是布爾表達式 。如果一個函數的所有 guard 都沒有通過(而且沒有提供 `otherwise` 作萬能匹配)，就轉入下一模式。這便是 guard 與模式契合的地方。如果始終沒有找到合適的 guard 或模式，就會發生一個錯誤。

當然，guard 可以在含有任意數量參數的函數中使用。省得用戶在使用這函數之前每次都自己計算 `bmi`。我們修改下這個函數，讓它取身高體重為我們計算。

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                 = "You're a whale, congratulations!"
```

你可以測試自己胖不胖。

```haskell
ghci> bmiTell 85 1.90  
"You're supposedly normal. Pffft, I bet you're ugly!"
```

運行的結果是我不太胖。不過程式卻說我很醜。

要注意一點，函數的名字和參數的後面並沒有 `=`。許多初學者會造成語法錯誤，就是因為在後面加上了 `=`。

另一個簡單的例子：寫個自己的 `max` 函數。應該還記得，它是取兩個可比較的值，返回較大的那個。

```haskell
max' :: (Ord a) => a -> a -> a  
max' a b   
    | a > b     = a  
    | otherwise = b
```

guard 也可以塞在一行裡面。但這樣會喪失可讀性，因此是不被鼓勵的。即使是較短的函數也是如此，不過出於展示，我們可以這樣重寫 `max'`：

```haskell
max' :: (Ord a) => a -> a -> a  
max' a b | a > b = a | otherwise = b
```

這樣的寫法根本一點都不容易讀。

我們再來試試用 guard 實現我們自己的 `compare` 函數：

```haskell
myCompare :: (Ord a) => a -> a -> Ordering  
a `myCompare` b  
    | a > b     = GT  
    | a == b    = EQ  
    | otherwise = LT
```

```haskell
ghci> 3 `myCompare` 2  
GT
```

```
*Note*：通過反單引號，我們不僅可以以中綴形式呼叫函數，也可以在定義函數的時候使用它。有時這樣會更易讀。
```

## 關鍵字 Where

前一節中我們寫了這個 `bmi` 計算函數：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                   = "You're a whale, congratulations!"
```

注意，我們重複了 3 次。我們重複了 3 次。程式設計師的字典裡不應該有"重複"這個詞。既然發現有重複，那麼給它一個名字來代替這三個表達式會更好些。嗯，我們可以這樣修改：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2
```

我們的 `where` 關鍵字跟在 guard 後面(最好是與豎綫縮進一致)，可以定義多個名字和函數。這些名字對每個 guard 都是可見的，這一來就避免了重複。如果我們打算換種方式計算 `bmi`，只需進行一次修改就行了。通過命名，我們提升了程式碼的可讀性，並且由於 `bmi` 只計算了一次，函數的執行效率也有所提升。我們可以再做下修改：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= skinny = "You're underweight, you emo, you!"  
    | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
    | otherwise     = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2  
          skinny = 18.5  
          normal = 25.0  
          fat = 30.0
```

函數在 `where` 綁定中定義的名字只對本函數可見，因此我們不必擔心它會污染其他函數的命名空間。注意，其中的名字都是一列垂直排開，如果不這樣規範，Haskell 就搞不清楚它們在哪個地方了。

`where` 綁定不會在多個模式中共享。如果你在一個函數的多個模式中重複用到同一名字，就應該把它置於全局定義之中。

`where` 綁定也可以使用*模式匹配*！前面那段程式碼可以改成：

```haskell
...  
where bmi = weight / height ^ 2  
      (skinny, normal, fat) = (18.5, 25.0, 30.0)
```

我們再搞個簡單函數，讓它告訴我們姓名的首字母：

```haskell
initials :: String -> String -> String  
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."  
    where (f:_) = firstname  
          (l:_) = lastname
```

我們完全按可以在函數的參數上直接使用模式匹配(這樣更短更簡潔)，在這裡只是為了演示在 `where` 語句中同樣可以使用模式匹配：

`where` 綁定可以定義名字，也可以定義函數。保持健康的程式語言風格，我們搞個計算一組 `bmi` 的函數：

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi w h | (w, h) <- xs] 
    where bmi weight height = weight / height ^ 2
```

這就全了！在這裡將 `bmi` 搞成一個函數，是因為我們不能依據參數直接進行計算，而必須先從傳入函數的 List 中取出每個序對並計算對應的值。

`where` 綁定還可以一層套一層地來使用。 有個常見的寫法是，在定義一個函數的時候也寫幾個輔助函數擺在 `where` 綁定中。 而每個輔助函數也可以透過 `where` 擁有各自的輔助函數。

## 關鍵字 Let

`let` 綁定與 `where` 綁定很相似。`where` 綁定是在函數底部定義名字，對包括所有 guard 在內的整個函數可見。`let` 綁定則是個表達式，允許你在任何位置定義局部變數，而對不同的 guard 不可見。正如 Haskell 中所有賦值結構一樣，`let` 綁定也可以使用模式匹配。看下它的實際應用！這是個依據半徑和高度求圓柱體表面積的函數：

```haskell
cylinder :: (RealFloat a) => a -> a -> a  
cylinder r h = 
    let sideArea = 2 * pi * r * h  
        topArea = pi * r ^2  
    in  sideArea + 2 * topArea
```

![](/files/-LjUmrZ8vZ9wJ-4u4iis)

`let` 的格式為 `let [bindings] in [expressions]`。在 `let` 中綁定的名字僅對 `in` 部分可見。`let` 裡面定義的名字也得對齊到一列。不難看出，這用 `where` 綁定也可以做到。那麼它倆有什麼區別呢？看起來無非就是，`let` 把綁定放在語句前面而 `where` 放在後面嘛。

不同之處在於，`let` 綁定本身是個表達式，而 `where` 綁定則是個語法結構。還記得前面我們講if語句時提到它是個表達式，因而可以隨處安放？

```haskell
ghci> [if 5 > 3 then "Woo" else "Boo", if 'a' > 'b' then "Foo" else "Bar"]  
["Woo", "Bar"]  
ghci> 4 * (if 10 > 5 then 10 else 0) + 2  
42
```

用 `let` 綁定也可以實現：

```haskell
ghci> 4 * (let a = 9 in a + 1) + 2  
42
```

`let` 也可以定義局部函數：

```haskell
ghci> [let square x = x * x in (square 5, square 3, square 2)]  
[(25,9,4)]
```

若要在一行中綁定多個名字，再將它們排成一列顯然是不可以的。不過可以用分號將其分開。

```haskell
ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)  
(6000000,"Hey there!")
```

最後那個綁定後面的分號不是必須的，不過加上也沒關係。如我們前面所說，你可以在 `let` 綁定中使用模式匹配。這在從 Tuple 取值之類的操作中很方便。

```haskell
ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100  
600
```

你也可以把 `let` 綁定放到 List Comprehension 中。我們重寫下那個計算 `bmi` 值的函數，用個 `let` 替換掉原先的 `where`。

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
```

List Comprehension 中 `let` 綁定的樣子和限制條件差不多，只不過它做的不是過濾，而是綁定名字。`let` 中綁定的名字在輸出函數及限制條件中都可見。這一來我們就可以讓我們的函數隻返回胖子的 `bmi` 值：

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
```

在 `(w, h) <- xs` 這裡無法使用 `bmi` 這名字，因為它在 `let` 綁定的前面。

在 List Comprehension 中我們忽略了 `let` 綁定的 `in` 部分，因為名字的可見性已經預先定義好了。不過，把一個 `let...in` 放到限制條件中也是可以的，這樣名字只對這個限制條件可見。在 ghci 中 `in` 部分也可以省略，名字的定義就在整個交互中可見。

```haskell
ghci> let zoot x y z = x * y + z  
ghci> zoot 3 9 2  
29  
ghci> let boot x y z = x * y + z in boot 3 4 2  
14  
ghci> boot  
< interactive>:1:0: Not in scope: `boot'
```

你說既然 `let` 已經這麼好了，還要 `where` 幹嘛呢？嗯，`let` 是個表達式，定義域限制的相當小，因此不能在多個 guard 中使用。一些朋友更喜歡 `where`，因為它是跟在函數體後面，把主函數體距離型別聲明近一些會更易讀。

## Case expressions

![](/files/-LjUmrZCzNw7SHIiVCpC)

有命令式程式語言 (C, C++, Java, etc.) 的經驗的同學一定會有所瞭解，很多命令式語言都提供了 `case` 語句。就是取一個變數，按照對變數的判斷選擇對應的程式碼塊。其中可能會存在一個萬能匹配以處理未預料的情況。

Haskell 取了這一概念融合其中。如其名，`case` 表達式就是，嗯，一種表達式。跟 `if..else` 和 `let` 一樣的表達式。用它可以對變數的不同情況分別求值，還可以使用模式匹配。Hmm，取一個變數，對它模式匹配，執行對應的程式碼塊。好像在哪兒聽過？啊，就是函數定義時參數的模式匹配！好吧，模式匹配本質上不過就是 `case` 語句的語法糖而已。這兩段程式碼就是完全等價的：

```haskell
head' :: [a] -> a  
head' [] = error "No head for empty lists!"  
head' (x:_) = x
```

```haskell
head' :: [a] -> a  
head' xs = case xs of [] -> error "No head for empty lists!"  
                      (x:_) -> x
```

看得出，*case*表達式的語法十分簡單：

```haskell
case expression of pattern -> result  
                   pattern -> result  
                   pattern -> result  
                   ...
```

expression 匹配合適的模式。 一如預期地，第一個模式若匹配，就執行第一個區塊的程式碼；否則就接下去比對下一個模式。如果到最後依然沒有匹配的模式，就會產生運行時錯誤。

函數參數的模式匹配只能在定義函數時使用，而 `case` 表達式可以用在任何地方。例如：

```haskell
describeList :: [a] -> String  
describeList xs = "The list is " ++ case xs of [] -> "empty."  
                                               [x] -> "a singleton list."   
                                               xs -> "a longer list."
```

這在表達式中作模式匹配很方便，由於模式匹配本質上就是 `case` 表達式的語法糖，那麼寫成這樣也是等價的：

```haskell
describeList :: [a] -> String  
describeList xs = "The list is " ++ what xs  
    where what [] = "empty."  
          what [x] = "a singleton list."  
          what xs = "a longer list."
```


# ch05


# 遞迴

## 你好，遞迴！

![](/files/-MSkgkYpU1HlR2ph4Mua)

前面的章節中我們簡要談了一下遞迴。而在本章，我們會深入地瞭解到它為何在 Haskell 中是如此重要，能夠以遞迴思想寫出簡潔優雅的程式碼。

如果你還不知道什麼是遞迴，就讀這個句子。哈哈！開個玩笑而已！遞迴實際上是定義函數以呼叫自身的方式。在數學定義中，遞迴隨處可見，如斐波那契數列 （fibonacci）。它先是定義兩個非遞迴的數：`F(0)=0,F(1)=1`，表示斐波那契數列的前兩個數為 0 和 1。然後就是對其他自然數，其斐波那契數就是它前面兩個數字的和，即 `F(N)=F(N-1)+F(N-2)`。這樣一來，`F(3)` 就是 `F(2)+F(1)`，進一步便是 `(F(1)+F(0))+F(1)`。已經下探到了前面定義的非遞迴斐波那契數，可以放心地說 `F(3)` 就是 2 了。在遞迴定義中聲明的一兩個非遞迴的值（如 `F(0)` 和 `F(1)`） 也可以稱作*邊界條件*，這對遞迴函數的正確求值至關重要。要是前面沒有定義 `F(0)` 和 `F(1)` 的話，它下探到 0 之後就會進一步到負數，你就永遠都得不到結果了。一不留神它就算到了 `F(-2000)=F(-2001)+F(-2002)`，並且永遠都算不到頭！

遞迴在 Haskell 中非常重要。命令式語言要求你提供求解的步驟，Haskell 則傾向于讓你提供問題的描述。這便是 Haskell 沒有 `while` 或 `for` 循環的原因，遞迴是我們的替代方案。

## 實作 Maximum

`maximum` 函數取一組可排序的 List（屬於 Ord Typeclass） 做參數，並回傳其中的最大值。想想，在命令式風格中這一函數該怎麼實現。很可能你會設一個變數來存儲當前的最大值，然後用循環遍歷該 List，若存在比這個值更大的元素，則修改變數為這一元素的值。到最後，變數的值就是運算結果。唔！描述如此簡單的算法還頗費了點口舌呢！

現在看看遞迴的思路是如何：我們先定下一個邊界條件，即處理單個元素的 List 時，回傳該元素。如果該 List 的頭部大於尾部的最大值，我們就可以假定較長的 List 的最大值就是它的頭部。而尾部若存在比它更大的元素，它就是尾部的最大值。就這麼簡單！現在，我們在 Haskell 中實現它

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' [] = error "maximum of empty list"  
maximum' [x] = x  
maximum' (x:xs)   
    | x > maxTail = x  
    | otherwise = maxTail  
    where maxTail = maximum' xs
```

如你所見，模式匹配與遞迴簡直就是天造地設！大多數命令式語言中都沒有模式匹配，於是你就得造一堆 if-else 來測試邊界條件。而在這裡，我們僅需要使用模式將其表示出來。第一個模式說，如果該 List 為空，崩潰！就該這樣，一個空 List 的最大值能是啥？我不知道。第二個模式也表示一個邊緣條件，它說， 如果這個 List 僅包含單個元素，就回傳該元素的值。

現在是第三個模式，執行動作的地方。 通過模式匹配，可以取得一個 List 的頭部和尾部。這在使用遞迴處理 List 時是十分常見的。出於習慣，我們用個 `where` 語句來表示 `maxTail` 作為該 List 中尾部的最大值，然後檢查頭部是否大於尾部的最大值。若是，回傳頭部；若非，回傳尾部的最大值。

我們取個 List `[2,5,1]` 做例子來看看它的工作原理。當呼叫 `maximum'` 處理它時，前兩個模式不會被匹配，而第三個模式匹配了它並將其分為 `2` 與 `[5,1]`。 `where` 子句再取 `[5,1]` 的最大值。於是再次與第三個模式匹配，並將 `[5,1]` 分割為 `5` 和 `[1]`。繼續，`where` 子句取 `[1]` 的最大值，這時終於到了邊緣條件！回傳 `1`。進一步，將 `5` 與 `[1]` 中的最大值做比較，易得 `5`，現在我們就得到了 `[5,1]` 的最大值。再進一步，將 `2` 與 `[5,1]` 中的最大值相比較，可得 `5` 更大，最終得 `5`。

改用 `max` 函數會使程式碼更加清晰。如果你還記得，`max` 函數取兩個值做參數並回傳其中較大的值。如下便是用 `max` 函數重寫的 `maximun'`

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' [] = error "maximum of empty list"  
maximum' [x] = x  
maximum' (x:xs) = max x (maximum' xs)
```

太漂亮了！一個 List 的最大值就是它的首個元素與它尾部中最大值相比較所得的結果，簡明扼要。

![](/files/-MSkgkYwMoN4O1Lv6EwF)

## 來看幾個遞迴函數

現在我們已經瞭解了遞迴的思路,接下來就使用遞迴來實現幾個函數. 先實現下 `replicate` 函數, 它取一個 `Int` 值和一個元素做參數, 回傳一個包含多個重複元素的 List, 如 `replicate 3 5` 回傳 `[5,5,5]`. 考慮一下, 我覺得它的邊界條件應該是負數. 如果要 `replicate` 重複某元素零次, 那就是空 List. 負數也是同樣, 不靠譜.

```haskell
replicate' :: (Num i, Ord i) => i -> a -> [a]  
replicate' n x  
    | n <= 0    = []  
    | otherwise = x:replicate' (n-1) x
```

在這裡我們使用了 guard 而非模式匹配, 是因為這裡做的是布林判斷. 如果 `n` 小於 0 就回傳一個空 List, 否則, 回傳以 `x` 作首個元素並後接重複 `n-1` 次 `x` 的 List. 最後, `(n-1)` 的那部分就會令函數抵達邊緣條件.

```
*Note*: Num 不是 Ord 的子集, 表示數字不一定得拘泥于排序, 這就是在做加減法比較時要將 Num 與 Ord 型別約束區別開來的原因.
```

接下來實現 `take` 函數, 它可以從一個 List 取出一定數量的元素. 如 `take 3 [5,4,3,2,1]`, 得 `[5,4,3]`. 若要取零或負數個的話就會得到一個空 List. 同樣, 若是從一個空 List中取值, 它會得到一個空 List. 注意, 這兒有兩個邊界條件, 寫出來:

```haskell
take' :: (Num i, Ord i) => i -> [a] -> [a]  
take' n _  
    | n <= 0   = []  
take' _ []     = []  
take' n (x:xs) = x : take' (n-1) xs
```

![](/files/-MSkgkYySSDtJyh72xoH)

首個模式辨認若為 0 或負數, 回傳空 List. 同時注意這裡用了一個 guard 卻沒有指定 `otherwise` 部分, 這就表示 `n` 若大於 0, 會轉入下一模式. 第二個模式指明了若試圖從一個空 List 中取值, 則回傳空 List. 第三個模式將 List 分割為頭部和尾部, 然後表明從一個 List 中取多個元素等同於令 `x` 作頭部後接從尾部取 `n-1` 個元素所得的 List. 假如我們要從 `[4,3,2,1]` 中取 3 個元素, 試着從紙上寫出它的推導過程

`reverse` 函數簡單地反轉一個 List, 動腦筋想一下它的邊界條件! 該怎樣呢? 想想...是空 List! 空 List 的反轉結果還是它自己. Okay, 接下來該怎麼辦? 好的, 你猜的出來. 若將一個 List 分割為頭部與尾部, 那它反轉的結果就是反轉後的尾部與頭部相連所得的 List.

```haskell
reverse' :: [a] -> [a]  
reverse' [] = []  
reverse' (x:xs) = reverse' xs ++ [x]
```

繼續下去!

Haskell 支持無限 List，所以我們的遞迴就不必添加邊界條件。這樣一來，它可以對某值計算個沒完, 也可以產生一個無限的資料結構，如無限 List。而無限 List 的好處就在於我們可以在任意位置將它斷開.

`repeat` 函數取一個元素作參數, 回傳一個僅包含該元素的無限 List. 它的遞迴實現簡單的很, 看:

```haskell
repeat' :: a -> [a]  
repeat' x = x:repeat' x
```

呼叫 `repeat 3` 會得到一個以 3 為頭部並無限數量的 3 為尾部的 List, 可以說 `repeat 3` 運行起來就是 `3:repeat 3` , 然後 `3:3:3:3` 等等. 若執行 `repeat 3`, 那它的運算永遠都不會停止。而 `take 5 (repeat 3)` 就可以得到 5 個 3, 與 `replicate 5 3` 差不多.

`zip` 取兩個 List 作參數並將其捆在一起。`zip [1,2,3] [2,3]` 回傳 `[(1,2),(2,3)]`, 它會把較長的 List 從中間斷開, 以匹配較短的 List. 用 `zip` 處理一個 List 與空 List 又會怎樣? 嗯, 會得一個空 List, 這便是我們的限制條件, 由於 `zip` 取兩個參數, 所以要有兩個邊緣條件

```haskell
zip' :: [a] -> [b] -> [(a,b)]  
zip' _ [] = []  
zip' [] _ = []  
zip' (x:xs) (y:ys) = (x,y):zip' xs ys
```

前兩個模式表示兩個 List 中若存在空 List, 則回傳空 List. 第三個模式表示將兩個 List 捆綁的行為, 即將其頭部配對並後跟捆綁的尾部. 用 `zip` 處理 `[1,2,3]` 與 `['a','b']` 的話, 就會在 `[3]` 與 `[]` 時觸及邊界條件, 得到 `(1,'a'):(2,'b'):[]` 的結果,與 `[(1,'a'),(2,'b')]` 等價.

再實現一個標準庫函數 -- `elem`! 它取一個元素與一個 List 作參數, 並檢測該元素是否包含于此 List. 而邊緣條件就與大多數情況相同, 空 List. 大家都知道空 List 中不包含任何元素, 便不必再做任何判斷

```haskell
elem' :: (Eq a) => a -> [a] -> Bool  
elem' a [] = False  
elem' a (x:xs)  
    | a == x    = True  
    | otherwise = a `elem'` xs
```

這很簡單明瞭。若頭部不是該元素, 就檢測尾部, 若為空 List 就回傳 `False`.

## "快速"排序

![](/files/-MSkgkZ-ydY5c0-GpFFi)

假定我們有一個可排序的 List, 其中元素的型別為 Ord Typeclass 的成員. 現在我們要給它排序! 有個排序算法非常的酷, 就是快速排序 (quick sort), 睿智的排序方法. 儘管它在命令式語言中也不過 10 行, 但在 Haskell 下邊要更短, 更漂亮, 儼然已經成了 Haskell 的招牌了. 嗯, 我們在這裡也實現一下. 或許會顯得很俗氣, 因為每個人都用它來展示 Haskell 究竟有多優雅!

它的型別聲明應為 `quicksort :: (Ord a) => [a] -> [a]`, 沒啥奇怪的. 邊界條件呢? 如料，空 List。排過序的空 List 還是空 List。接下來便是算法的定義：*排過序的 List 就是令所有小於等於頭部的元素在先(它們已經排過了序), 後跟大於頭部的元素(它們同樣已經拍過了序)*。 注意定義中有兩次排序，所以就得遞迴兩次！同時也需要注意算法定義的動詞為"是"什麼而非"做"這個, "做"那個, 再"做"那個...這便是函數式編程之美！如何才能從 List 中取得比頭部小的那些元素呢？List Comprehension。好，動手寫出這個函數！

```haskell
quicksort :: (Ord a) => [a] -> [a]  
quicksort [] = []  
quicksort (x:xs) =  
  let smallerSorted = quicksort [a | a <- xs, a <= x] 
      biggerSorted = quicksort [a | a <- xs, a > x]  
  in smallerSorted ++ [x] ++ biggerSorted
```

小小的測試一下, 看看結果是否正確\~

```haskell
ghci> quicksort [10,2,5,3,1,6,7,4,2,3,4,8,9]  
[1,2,2,3,3,4,4,5,6,7,8,9,10]  
ghci> quicksort "the quick brown fox jumps over the lazy dog"  
" abcdeeefghhijklmnoooopqrrsttuuvwxyz"
```

booyah! 如我所說的一樣! 若給 `[5,1,9,4,6,7,3]` 排序，這個算法就會取出它的頭部，即 5。 將其置於分別比它大和比它小的兩個 List 中間，得 `[1,4,3] ++ [5] ++ [9,6,7]`, 我們便知道了當排序結束之時，5會在第四位，因為有3個數比它小，也有三個數比它大。好的，接着排 `[1,4,3]` 與 `[9,6,7]`, 結果就出來了！對它們的排序也是使用同樣的函數，將它們分成許多小塊，最終到達臨界條件，即空 List 經排序依然為空，有個插圖：

橙色的部分表示已定位並不再移動的元素。從左到右看，便是一個排過序的 List。在這裡我們將所有元素與 `head` 作比較，而實際上就快速排序算法而言，選擇任意元素都是可以的。被選擇的元素就被稱作錨 （`pivot`），以方便模式匹配。小於錨的元素都在淺綠的部分，大於錨都在深綠部分，這個黃黃的坡就表示了快速排序的執行方式：

![](/files/-MSkgkZ1mvxhfwGnpNBu)

## 用遞迴來思考

我們已經寫了不少遞迴了，也許你已經發覺了其中的固定模式：先定義一個邊界條件，再定義個函數，讓它從一堆元素中取一個並做點事情後，把餘下的元素重新交給這個函數。 這一模式對 List、Tree 等資料結構都是適用的。例如，`sum` 函數就是一個 List 頭部與其尾部的 `sum` 的和。一個 List 的積便是該 List 的頭與其尾部的積相乘的積，一個 List 的長度就是 1 與其尾部長度的和. 等等

![](/files/-MSkgkZ276wdggp-g3Ck)

再者就是邊界條件。一般而言，邊界條件就是為避免程序出錯而設置的保護措施，處理 List 時的邊界條件大部分都是空 List，而處理 Tree 時的邊界條件就是沒有子元素的節點。

處理數字時也與之相似。函數一般都得接受一個值並修改它。早些時候我們編寫過一個計算 Factorial 的函數，它便是某數與它減一的 Factorial 數的積。讓它乘以零就不行了， Factorial 數又都是非負數，邊界條件便可以定為 1，即乘法的單位元。 因為任何數乘以 1 的結果還是這個數。而在 `sum` 中，加法的單位元就是 0。在快速排序中，邊界條件和單位元都是空 List，因為任一 List 與空 List 相加的結果依然是原 List。

使用遞迴來解決問題時應當先考慮遞迴會在什麼樣的條件下不可用, 然後再找出它的邊界條件和單位元, 考慮參數應該在何時切開(如對 List 使用模式匹配), 以及在何處執行遞迴.


# ch06


# 高階函數

![](/files/-MSkgjtGOvic9arTx6Dm) Haskell 中的函數可以接受函數作為參數也可以返回函數作為結果，這樣的函數就被稱作高階函數。高階函數可不只是某簡單特性而已，它貫穿于 Haskell 的方方面面。要拒絶循環與狀態的改變而通過定義問題"是什麼"來解決問題，高階函數必不可少。它們是編碼的得力工具。

## Curried functions

本質上，Haskell 的所有函數都只有一個參數，那麼我們先前編那麼多含有多個參數的函數又是怎麼回事? 呵，小伎倆! 所有多個參數的函數都是 Curried functions。 什麼意思呢? 取一個例子最好理解，就拿我們的好朋友 `max` 函數說事吧。它看起來像是取兩個參數，回傳較大的那個數。 實際上，執行 `max 4 5` 時，它會首先回傳一個取一個參數的函數，其回傳值不是 4 就是該參數，取決於誰大。 然後，以 5 為參數呼叫它，並取得最終結果。 這聽著挺繞口的，不過這一概念十分的酷! 如下的兩個呼叫是等價的：

```haskell
ghci> max 4 5
5
ghci> (max 4) 5
5
```

![](/files/-MSkgjtIi3iBjHeR1GRK)

把空格放到兩個東西之間，稱作*函數呼叫*。它有點像個運算符，並擁有最高的優先順序。 看看 `max` 函數的型別: `max :: (Ord a) => a -> a -> a`。 也可以寫作: `max :: (Ord a) => a -> (a -> a)`。 可以讀作 `max` 取一個參數 `a`，並回傳一個函數(就是那個 `->`)，這個函數取一個 `a` 型別的參數，回傳一個a。 這便是為何只用箭頭來分隔參數和回傳值型別。

這樣的好處又是如何? 簡言之，我們若以不全的參數來呼叫某函數，就可以得到一個*不全呼叫*的函數。 如果你高興，構造新函數就可以如此便捷，將其傳給另一個函數也是同樣方便。

看下這個函數，簡單至極:

```haskell
multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z
```

我們若執行 `mulThree 3 5 9` 或 `((mulThree 3) 5) 9`，它背後是如何運作呢？ 首先，按照空格分隔，把 `3` 交給 `mulThree`。 這回傳一個回傳函數的函數。 然後把 `5` 交給它，回傳一個取一個參數並使之乘以 `15` 的函數。 最後把 `9` 交給這一函數，回傳 `135`。 想想，這個函數的型別也可以寫作 `multThree :: (Num a) => a -> (a -> (a -> a))`，`->` 前面的東西就是函數取的參數，後面的東西就是其回傳值。所以說，我們的函數取一個 `a`，並回傳一個型別為 `(Num a) => a -> (a -> a)` 的函數，類似，這一函數回傳一個取一個 `a`，回傳一個型別為 `(Num a) => a -> a` 的函數。 而最後的這個函數就只取一個 `a` 並回傳一個 `a`，如下:

```haskell
ghci> let multTwoWithNine = multThree 9
ghci> multTwoWithNine 2 3
54
ghci> let multWithEighteen = multTwoWithNine 2
ghci> multWithEighteen 10
180
```

前面提到，以不全的參數呼叫函數可以方便地創造新的函數。例如，搞個取一數與 100 比較大小的函數該如何? 大可這樣:

```haskell
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred x = compare 100 x
```

用 99 呼叫它，就可以得到一個 `GT`。 簡單。 注意下在等號兩邊都有 `x`。 想想 `compare 100` 會回傳什麼？一個取一數與 100 比較的函數。 Wow，這不正是我們想要的? 這樣重寫:

```haskell
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred = compare 100
```

型別聲明依然相同，因為 `compare 100` 回傳函數。`compare` 的型別為 `(Ord a) => a -> (a -> Ordering)`，用 100 呼叫它後回傳的函數型別為 `(Num a, Ord a) => a -> Ordering`，同時由於 100 還是 `Num` 型別類的實例，所以還得另留一個類約束。

Yo! 你得保證已經弄明白了 Curried functions 與不全呼叫的原理，它們很重要！

中綴函數也可以不全呼叫，用括號把它和一邊的參數括在一起就行了。 這回傳一個取一參數並將其補到缺少的那一端的函數。 一個簡單函數如下:

```haskell
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)
```

呼叫 `divideByTen 200` 就是 `(/10) 200`，和 `200 / 10` 等價。

一個檢查字元是否為大寫的函數:

```haskell
isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])
```

唯一的例外就是 `-` 運算符，按照前面提到的定義，`(-4)` 理應回傳一個並將參數減 4 的函數，而實際上，處于計算上的方便，`(-4)` 表示負 `4`。 若你一定要弄個將參數減 4 的函數，就用 `subtract` 好了，像這樣 `(subtract 4)`.

若不用 `let` 給它命名或傳到另一函數中，在 ghci 中直接執行 `multThree 3 4` 會怎樣?

```haskell
ghci> multThree 3 4
:1:0:
No instance for (Show (t -> t))
arising from a use of `print' at :1:0-12
Possible fix: add an instance declaration for (Show (t -> t))
In the expression: print it
In a 'do' expression: print it
```

ghci 說，這一表達式回傳了一個 `a -> a` 型別的函數，但它不知道該如何顯示它。 函數不是 `Show` 型別類的實例，所以我們不能得到表示一函數內容的字串。 若在 ghci 中計算 `1+1`，它會首先計算得 `2`，然後呼叫 `show 2` 得到該數值的字串表示，即 `"2"`，再輸出到屏幕.

## 是時候了，來點高階函數！

Haskell 中的函數可以取另一個函數做參數，也可以回傳函數。 舉個例子，我們弄個取一個函數並呼叫它兩次的函數.

```haskell
applyTwice :: (a -> a) -> a -> a  
applyTwice f x = f (f x)
```

![](/files/-MSkgjtKhlIT9aQWovc3)

首先注意這型別聲明。 在此之前我們很少用到括號，因為 `(->)` 是自然的右結合，不過在這裡括號是必須的。 它標明了首個參數是個參數與回傳值型別都是a的函數，第二個參數與回傳值的型別也都是a。 我們可以用 Curried functions 的思路來理解這一函數，不過免得自尋煩惱，我們姑且直接把它看作是取兩個參數回傳一個值，其首個參數是個型別為 `(a->a)` 的函數,第二個參數是個 `a`。 該函數的型別可以是 `(Int->Int)`，也可以是 `(String->String)`，但第二個參數必須與之一致。

```
*Note*: 現在開始我們會直說某函數含有多個參數(除非它真的只有一個參數)。 以簡潔之名，我們會說 ``(a->a->a)`` 取兩個參數，儘管我們知道它在背後做的手腳.
```

這個函數是相當的簡單，就拿參數 `f` 當函數，用 `x` 呼叫它得到的結果再去呼叫它。也就可以這樣玩:

```haskell
ghci> applyTwice (+3) 10  
16  
ghci> applyTwice (++ " HAHA") "HEY"  
"HEY HAHA HAHA"  
ghci> applyTwice ("HAHA " ++) "HEY"  
"HAHA HAHA HEY"  
ghci> applyTwice (multThree 2 2) 9  
144  
ghci> applyTwice (3:) [1]  
[3,3,1]
```

看，不全呼叫多神奇! 如果有個函數要我們給它傳個一元函數，大可以不全呼叫一個函數讓它剩一個參數，再把它交出去。

接下來我們用高階函數的編程思想來實現個標準庫中的函數，它就是 `zipWith`。 它取一個函數和兩個 List 做參數，並把兩個 List 交到一起(使相應的元素去呼叫該函數)。 如下就是我們的實現:

```haskell
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]  
zipWith' _ [] _ = []  
zipWith' _ _ [] = []  
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
```

看下這個型別聲明，它的首個參數是個函數，取兩個參數處理交叉，其型別不必相同，不過相同也沒關係。 第二三個參數都是 List，回傳值也是個 List。 第一個 List中元素的型別必須是a，因為這個處理交叉的函數的第一個參數是a。 第二個 List 中元素的型別必為 `b`，因為這個處理交叉的函數第二個參數的型別是 `b`。 回傳的 List 中元素型別為 `c`。 如果一個函數說取一個型別為 `a->b->c` 的函數做參數，傳給它個 `a->a->c` 型別的也是可以的，但反過來就不行了。 可以記下，若在使用高階函數的時候不清楚其型別為何，就先忽略掉它的型別聲明，再到 ghci 下用 `:t` 命令來看下 Haskell 的型別推導.

這函數的行為與普通的 `zip` 很相似，邊界條件也是相同，只不過多了個參數，即處理元素交叉的函數。它關不着邊界條件什麼事兒，所以我們就只留一個 `_`。後一個模式的函數體與 `zip` 也很像，只不過這裡是 `f x y` 而非 `(x,y)`。 只要足夠通用，一個簡單的高階函數可以在不同的場合反覆使用。 如下便是我們 `zipWith'` 函數本領的冰山一角:

```haskell
ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]  
[6,8,7,9]  
ghci> zipWith' max [6,3,2,1] [7,3,1,5]  
[7,3,2,5]  
ghci> zipWith' (++) ["foo "，"bar "，"baz "] ["fighters"，"hoppers"，"aldrin"]  
["foo fighters","bar hoppers","baz aldrin"]  
ghci> zipWith' (*) (replicate 5 2) [1..]  
[2,4,6,8,10]  
ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]  
[[3,4,6],[9,20,30],[10,12,12]]
```

如你所見，一個簡單的高階函數就可以玩出很多花樣。命令式語言使用 `for`、`while`、賦值、狀態檢測來實現功能，再包起來留個介面，使之像個函數一樣呼叫。而函數式語言使用高階函數來抽象出常見的模式，像成對遍歷並處理兩個 List 或從中篩掉自己不需要的結果。

接下來實現標準庫中的另一個函數 `flip`，`flip`簡單地取一個函數作參數並回傳一個相似的函數，只是它們的兩個參數倒了個。

```haskell
flip' :: (a -> b -> c) -> (b -> a -> c)  
flip' f = g  
    where g x y = f y x
```

從這型別聲明中可以看出，它取一個函數，其參數型別分別為 `a` 和 `b`，而它回傳的函數的參數型別為 `b` 和 `a`。 由於函數預設都是柯裡化的，`->` 為右結合，這裡的第二對括號其實並無必要，`(a -> b -> c) -> (b -> a -> c)` 與 `(a -> b -> c) -> (b -> (a -> c))` 等價,也與 `(a -> b -> c) -> b -> a -> c` 等價。 前面我們寫了 `g x y = f y x`，既然這樣可行，那麼 `f y x = g x y` 不也一樣? 這一來我們可以改成更簡單的寫法:

```haskell
flip' :: (a -> b -> c) -> b -> a -> c  
flip' f y x = f x y
```

在這裡我們就利用了 Curried functions 的優勢，只要呼叫 `flip' f` 而不帶 `y`和`x`，它就會回傳一個倆參數倒個的函數。 `flip` 處理的函數往往都是用來傳給其他函數呼叫，於是我們可以發揮 Curried functions 的優勢，預先想好發生完全呼叫的情景並處理好回傳值.

```haskell
ghci> flip' zip [1,2,3,4,5] "hello"  
[('h',1),('e',2),('l',3),('l',4),('o',5)]  
ghci> zipWith (flip' div) [2,2..] [10,8,6,4,2]  
[5,4,3,2,1]
```

## map 與 filter

**map** 取一個函數和 List 做參數，遍歷該 List 的每個元素來呼叫該函數產生一個新的 List。 看下它的型別聲明和實現:

```haskell
map :: (a -> b) -> [a] -> [b]  
map _ [] = []  
map f (x:xs) = f x : map f xs
```

從這型別聲明中可以看出，它取一個取 `a` 回傳 `b` 的函數和一組 `a` 的 List，並回傳一組 `b`。 這就是 Haskell 的有趣之處：有時只看型別聲明就能對函數的行為猜個大致。`map` 函數多才多藝，有一百萬種用法。如下是其中一小部分:

```haskell
ghci> map (+3) [1,5,3,1,6]  
[4,8,6,4,9]  
ghci> map (++ "!") ["BIFF"，"BANG"，"POW"]  
["BIFF!","BANG!","POW!"]  
ghci> map (replicate 3) [3..6]  
[[3,3,3],[4,4,4],[5,5,5],[6,6,6]]  
ghci> map (map (^2)) [[1,2],[3,4,5,6],[7,8]]  
[[1,4],[9,16,25,36],[49,64]]  
ghci> map fst [(1,2),(3,5),(6,3),(2,6),(2,5)]  
[1,3,6,2,2]
```

你可能會發現，以上的所有程式碼都可以用 List Comprehension 來替代。`map (+3) [1,5,3,1,6]` 與 `[x+3 | x <- [1,5,3,1,6]` 完全等價。

**filter** 函數取一個限制條件和一個 List，回傳該 List 中所有符合該條件的元素。它的型別聲明及實現大致如下:

```haskell
filter :: (a -> Bool) -> [a] -> [a]  
filter _ [] = []  
filter p (x:xs)   
    | p x       = x : filter p xs  
    | otherwise = filter p xs
```

很簡單。只要 `p x` 所得的結果為真，就將這一元素加入新 List，否則就無視之。幾個使用範例:

```haskell
ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1]  
[5,6,4]  
ghci> filter (==3) [1,2,3,4,5]  
[3]  
ghci> filter even [1..10]  
[2,4,6,8,10]  
ghci> let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]]  
[[1,2,3],[3,4,5],[2,2]]  
ghci> filter (`elem` ['a'..'z']) "u LaUgH aT mE BeCaUsE I aM diFfeRent"  
"uagameasadifeent"  
ghci> filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same"  
"GAYBALLS"
```

同樣，以上都可以用 List Comprehension 的限制條件來實現。並沒有教條規定你必須在什麼情況下用 `map` 和 `filter` 還是 List Comprehension，選擇權歸你，看誰舒服用誰就是。 如果有多個限制條件，只能連着套好幾個 `filter` 或用 `&&` 等邏輯函數的組合之，這時就不如 List comprehension 來得爽了。

還記得上一章的那個 `quicksort` 函數麼? 我們用到了 List Comprehension 來過濾大於或小於錨的元素。 換做 `filter` 也可以實現，而且更加易讀：

```haskell
quicksort :: (Ord a) => [a] -> [a]    
quicksort [] = []    
quicksort (x:xs) =     
    let smallerSorted = quicksort (filter (<=x) xs)
        biggerSorted = quicksort (filter (>x) xs)   
    in  smallerSorted ++ [x] ++ biggerSorted
```

![](/files/-MSkgjtMnVrWFwcn0oxc)

`map` 和 `filter` 是每個函數式程式設計師的麵包黃油(呃，`map` 和 `filter` 還是 List Comprehension 並不重要)。 想想前面我們如何解決給定周長尋找合適直角三角形的問題的? 在命令式編程中，我們可以套上三個循環逐個測試當前的組合是否滿足條件，若滿足，就打印到屏幕或其他類似的輸出。 而在函數式編程中，這行就都交給 `map` 和 `filter`。 你弄個取一參數的函數，把它交給 `map` 過一遍 List，再 `filter` 之找到合適的結果。 感謝 Haskell 的惰性，即便是你多次 `map` 一個 `` `List `` 也只會遍歷一遍該 List，要找出小於 100000 的數中最大的 3829 的倍數，只需過濾結果所在的 List 就行了.

要找出*小於 100000 的 3829 的所有倍數*，我們應當過濾一個已知結果所在的 List.

```haskell
largestDivisible :: (Integral a) => a  
largestDivisible = head (filter p [100000,99999..])  
    where p x = x `mod` 3829 == 0
```

首先，取一個降序的小於 100000 所有數的 List，然後按照限制條件過濾它。 由於這個 List 是降序的，所以結果 List 中的首個元素就是最大的那個數。惰性再次行動! 由於我們只取這結果 List 的首個元素，所以它並不關心這 List 是有限還是無限的，在找到首個合適的結果處運算就停止了。

接下來，我們就要*找出所有小於 10000 且为奇的平方的和*，得先提下 **takeWhile** 函數，它取一個限制條件和 List 作參數，然後從頭開始遍歷這一 List，並回傳符合限制條件的元素。 而一旦遇到不符合條件的元素，它就停止了。 如果我們要取出字串 `"elephants know how to party"` 中的首個單詞，可以 `takeWhile (/=' ') "elephants know how to party"`，回傳 `"elephants"`。okay，要求所有小於 10000 的奇數的平方的和，首先就用 `(^2)` 函數 `map` 掉這個無限的 List `[1..]` 。然後過濾之，只取奇數就是了。 在大於 10000 處將它斷開，最後前面的所有元素加到一起。 這一切連寫函數都不用，在 ghci 下直接搞定.

```haskell
ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..])))  
166650
```

不錯! 先從幾個初始數據(表示所有自然數的無限 List)，再 `map` 它，`filter` 它，切它，直到它符合我們的要求，再將其加起來。 這用 List comprehension 也是可以的，而哪種方式就全看你的個人口味.

```haskell
ghci> sum (takeWhile (<10000) [m | m <- [n^2 | n <- [1..]], odd m])  
166650
```

感謝 Haskell 的惰性特質，這一切才得以實現。 我們之所以可以 `map` 或 `filter` 一個無限 List，是因為它的操作不會被立即執行，而是拖延一下。只有我們要求 Haskell 交給我們 `sum` 的結果的時候，`sum` 函數才會跟 `takeWhile` 說，它要這些數。`takeWhile` 就再去要求 `filter` 和 `map` 行動起來，並在遇到大於等於 10000 時候停止. 下個問題與 Collatz 序列有關，取一個自然數，若為偶數就除以 2。 若為奇數就乘以 3 再加 1。 再用相同的方式處理所得的結果，得到一組數字構成的的鏈。它有個性質，無論任何以任何數字開始，最終的結果都會歸 1。所以若拿 13 當作起始數，就可以得到這樣一個序列 `13，40，20，10，5，16，8，4，2，1`。`13*3+1` 得 40，40 除 2 得 20，如是繼續，得到一個 10 個元素的鏈。

好的，我們想知道的是: 以 1 到 100 之間的所有數作為起始數，會有多少個鏈的長度大於 15?

```haskell
chain :: (Integral a) => a -> [a]  
chain 1 = [1]  
chain n  
    | even n =  n:chain (n `div` 2)  
    | odd n  =  n:chain (n*3 + 1)
```

該鏈止於 1，這便是邊界條件。標準的遞歸函數:

```haskell
ghci> chain 10  
[10,5,16,8,4,2,1]  
ghci> chain 1  
[1]  
ghci> chain 30  
[30,15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]
```

yay! 貌似工作良好。 現在由這個函數來告訴我們結果:

```haskell
numLongChains :: Int  
numLongChains = length (filter isLong (map chain [1..100]))  
    where isLong xs = length xs > 15
```

我們把 `chain` 函數 `map` 到 `[1..100]`，得到一組鏈的 List，然後用個限制條件過濾長度大於 15 的鏈。過濾完畢後就可以得出結果list中的元素個數.

```
*Note*: 這函數的型別為 ``numLongChains :: Int``。這是由於歷史原因，``length`` 回傳一個 ``Int`` 而非 ``Num`` 的成員型別，若要得到一個更通用的 ``Num a``，我們可以使用 ``fromIntegral`` 函數來處理所得結果.
```

用 `map`，我們可以寫出類似 `map (*) [0..]` 之類的程式碼。 如果只是為了例證 Curried functions 和不全呼叫的函數是真正的值及其原理，那就是你可以把函數傳遞或把函數裝在 List 中(只是你還不能將它們轉換為字串)。 迄今為止，我們還只是 `map` 單參數的函數到 List，如 `map (*2) [0..]` 可得一組型別為 `(Num a) => [a]` 的 List，而 `map (*) [0..]` 也是完全沒問題的。`*` 的型別為 `(Num a) => a -> a -> a`，用單個參數呼叫二元函數會回傳一個一元函數。如果用 `*` 來 `map` 一個 `[0..]` 的 List，就會得到一組一元函數組成的 List，即 `(Num a) => [a->a]`。`map (*) [0..]` 所得的結果寫起來大約就是 `[(0*),(1*),(2*)..]`.

```haskell
ghci> let listOfFuns = map (*) [0..]  
ghci> (listOfFuns !! 4) 5  
20
```

取所得 List 的第五個元素可得一函數，與 `(*4)` 等價。 然後用 `5` 呼叫它，與 `(* 4) 5` 或 `4*5` 都是等價的.

## lambda

![](/files/-MSkgjtPITbz2QlItMLc)

lambda 就是匿名函數。有些時候我們需要傳給高階函數一個函數，而這函數我們只會用這一次，這就弄個特定功能的 lambda。編寫 lambda，就寫個 `\` (因為它看起來像是希臘字母的 lambda -- 如果你斜視的厲害)，後面是用空格分隔的參數，`->` 後面就是函數體。通常我們都是用括號將其括起，要不然它就會佔據整個右邊部分。

向上 5 英吋左右，你會看到我們在 `numLongChain` 函數中用 `where` 語句聲明了個 `isLong` 函數傳遞給了 `filter`。好的，用 lambda 代替它。

```haskell
numLongChains :: Int  
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))
```

![](/files/-MSkgjtQHQUPTKuTHrFY)

lambda 是個表達式，因此我們可以任意傳遞。表達式 `(\xs -> length xs > 15)` 回傳一個函數，它可以告訴我們一個 List 的長度是否大於 15。

不熟悉 Curried functions 與不全呼叫的人們往往會寫出很多 lambda，而實際上大部分都是沒必要的。例如，表達式 `map (+3) [1,6,3,2]` 與 `map (\x -> x+3) [1,6,3,2]` 等價，`(+3)` 和 `(\x -> x+3)` 都是給一個數加上 3。不用說，在這種情況下不用 lambda 要清爽的多。

和普通函數一樣，lambda 也可以取多個參數。

```haskell
ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]  
[153.0,61.5,31.0,15.75,6.6]
```

同普通函數一樣，你也可以在 lambda 中使用模式匹配，只是你無法為一個參數設置多個模式，如 `[]` 和 `(x:xs)`。lambda 的模式匹配若失敗，就會引發一個運行時錯誤，所以慎用！

```haskell
ghci> map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)]  
[3,8,9,8,7]
```

一般情況下，lambda 都是括在括號中，除非我們想要後面的整個語句都作為 lambda 的函數體。很有趣，由於有柯裡化，如下的兩段是等價的：

```haskell
addThree :: (Num a) => a -> a -> a -> a  
addThree x y z = x + y + z
```

```haskell
addThree :: (Num a) => a -> a -> a -> a  
addThree = \x -> \y -> \z -> x + y + z
```

這樣的函數聲明與函數體中都有 `->`，這一來型別聲明的寫法就很明白了。當然第一段程式碼更易讀，不過第二個函數使得柯裡化更容易理解。

有些時候用這種語句寫還是挺酷的，我覺得這應該是最易讀的 `flip` 函數實現了：

```haskell
flip' :: (a -> b -> c) -> b -> a -> c  
flip' f = \x y -> f y x
```

儘管這與 `flip' f x y = f y x` 等價，但它可以更明白地表示出它會產生一個新的函數。`flip` 常用來處理一個函數，再將回傳的新函數傳遞給 `map` 或 `filter`。所以如此使用 lambda 可以更明確地表現出回傳值是個函數，可以用來傳遞給其他函數作參數。

## 關鍵字 fold

![](/files/-MSkgjtTfivFfJC07Myd)

回到當初我們學習遞歸的情景。我們會發現處理 List 的許多函數都有固定的模式，通常我們會將邊界條件設置為空 List，再引入 `(x:xs)` 模式，對單個元素和餘下的 List 做些事情。這一模式是如此常見，因此 Haskell 引入了一組函數來使之簡化，也就是 `fold`。它們與map有點像，只是它們回傳的是單個值。

一個 `fold` 取一個二元函數，一個初始值(我喜歡管它叫累加值)和一個需要摺疊的 List。這個二元函數有兩個參數，即累加值和 List 的首項(或尾項)，回傳值是新的累加值。然後，以新的累加值和新的 List 首項呼叫該函數，如是繼續。到 List 遍歷完畢時，只剩下一個累加值，也就是最終的結果。

首先看下 **foldl** 函數，也叫做左摺疊。它從 List 的左端開始摺疊，用初始值和 List 的頭部呼叫這二元函數，得一新的累加值，並用新的累加值與 List 的下一個元素呼叫二元函數。如是繼續。

我們再實現下 `sum`，這次用 `fold` 替代那複雜的遞歸：

```haskell
sum' :: (Num a) => [a] -> a  
sum' xs = foldl (\acc x -> acc + x) 0 xs
```

測試下，一二三～

```haskell
ghci> sum' [3,5,2,1]  
11
```

![](/files/-MSkgjtUj8RaH9Qejpvt)

我們深入看下 `fold` 的執行過程：`\acc x-> acc + x` 是個二元函數，`0` 是初始值，`xs` 是待摺疊的 List。一開始，累加值為 `0`，當前項為 `3`，呼叫二元函數 `0+3` 得 `3`，作新的累加值。接着來，累加值為 `3`，當前項為 `5`，得新累加值 `8`。再往後，累加值為 `8`，當前項為 `2`，得新累加值 `10`。最後累加值為 `10`，當前項為 `1`，得 `11`。恭喜，你完成了一次摺疊 `(fold)`！

左邊的這個圖表示了摺疊的執行過程，一步又一步(一天又一天!)。淺棕色的數字都是累加值，你可以從中看出 List 是如何從左端一點點加到累加值上的。唔對對對！如果我們考慮到函數的柯裡化，可以寫出更簡單的實現：

```haskell
sum' :: (Num a) => [a] -> a  
sum' = foldl (+) 0
```

這個 lambda 函數 `(\acc x -> acc + x )` 與 `(+)` 等價。我們可以把 `xs` 等一應參數省略掉，反正呼叫 `foldl (+) 0` 會回傳一個取 List 作參數的函數。通常，如果你的函數類似 `foo a = bar b a`， 大可改為 `foo = bar b`。有柯裡化嘛。

呼呼，進入右摺疊前我們再實現個用到左摺疊的函數。大家肯定都知道 `elem` 是檢查某元素是否屬於某 List 的函數吧，我就不再提了(唔，剛提了)。用左摺疊實現它:

```haskell
elem' :: (Eq a) => a -> [a] -> Bool  
elem' y ys = foldl (\acc x -> if x == y then True else acc) False ys
```

好好好，這裡我們有什麼？起始值與累加值都是布爾值。在處理 `fold` 時，累加值與最終結果的型別總是相同的。如果你不知道怎樣對待起始值，那我告訴你，我們先假設它不存在，以 `False` 開始。我們要是 `fold` 一個空 List，結果就是 `False`。然後我們檢查當前元素是否為我們尋找的，如果是，就令累加值為 `True`，如果否，就保留原值不變。若 `False`，及表明當前元素不是。若 `True`，就表明已經找到了。

右摺疊 **foldr** 的行為與左摺疊相似，只是累加值是從 List 的右邊開始。同樣，左摺疊的二元函數取累加值作首個參數，當前值為第二個參數(即 `\acc x -> ...`)，而右摺疊的二元函數參數的順序正好相反(即 `\x acc -> ...`)。這倒也正常，畢竟是從右端開始摺疊。

累加值可以是任何型別，可以是數值，布爾值，甚至一個新的 List。我們可以用右 `fold` 實現 `map` 函數，累加值就是個 List。將 `map` 處理過的元素一個一個連到一起。很容易想到，起始值就是空 List。

```haskell
map' :: (a -> b) -> [a] -> [b]  
map' f xs = foldr (\x acc -> f x : acc) [] xs
```

如果我們用 `(+3)` 來映射 `[1,2,3]`，它就會先到達 List 的右端，我們取最後那個元素，也就是 `3` 來呼叫 `(+3)`，得 `6`。追加 `(:)` 到累加值上，`6:[]` 得 `[6]` 並成為新的累加值。用 `2` 呼叫 `(+3)`，得 `5`，追加到累加值，於是累加值成了 `[5,6]`。再對 `1` 呼叫 `(+3)`，並將結果 4 追加到累加值，最終得結果 `[4,5,6]`。

當然，我們也完全可以用左摺疊來實現它，`map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs` 就行了。不過問題是，使用 `(++)` 往 List 後面追加元素的效率要比使用 `(:)` 低得多。所以在生成新 List 的時候人們一般都是使用右摺疊。

![](/files/-MSkgjtXyswJjgAs-Wjc)

反轉一個 List，既也可以通過右摺疊，也可以通過左摺疊。有時甚至不需要管它們的分別，如 `sum` 函數的左右摺疊實現都是十分相似。不過有個大的不同，那就是右摺疊可以處理無限長度的資料結構，而左摺疊不可以。將無限 List 從中斷開執行左摺疊是可以的，不過若是向右，就永遠到不了頭了。

*所有遍歷 List 中元素並據此回傳一個值的操作都可以交給 `fold` 實現*。無論何時需要遍歷 List 並回傳某值，都可以嘗試下 `fold`。因此，`fold`的地位可以說與 `map`和 `filter`並駕齊驅，同為函數式編程中最常用的函數之一。

**foldl1** 與 **foldr1** 的行為與 `foldl` 和 `foldr` 相似，只是你無需明確提供初始值。他們假定 List 的首個(或末尾)元素作為起始值，並從旁邊的元素開始摺疊。這一來，`sum` 函數大可這樣實現：`sum = foldl1 (+)`。這裡待摺疊的 List 中至少要有一個元素，若使用空 List 就會產生一個運行時錯誤。不過 `foldl` 和 `foldr` 與空 List 相處的就很好。所以在使用 `fold` 前，應該先想下它會不會遇到空 List，如果不會遇到，大可放心使用 `foldr1` 和 `foldl1`。

為了體會 `fold` 的威力，我們就用它實現幾個庫函數：

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' = foldr1 (\x acc -> if x > acc then x else acc)  

reverse' :: [a] -> [a]  
reverse' = foldl (\acc x -> x : acc) []  

product' :: (Num a) => [a] -> a  
product' = foldr1 (*)  

filter' :: (a -> Bool) -> [a] -> [a]  
filter' p = foldr (\x acc -> if p x then x : acc else acc) []  

head' :: [a] -> a  
head' = foldr1 (\x _ -> x)  

last' :: [a] -> a  
last' = foldl1 (\_ x -> x)
```

僅靠模式匹配就可以實現 `head` 函數和 `last` 函數，而且效率也很高。這裡只是為了演示，用 `fold` 的實現方法。我覺得我們這個 `reverse'` 定義的相當聰明，用一個空 List 做初始值，並向左展開 List，從左追加到累加值，最後得到一個反轉的新 List。`\acc x -> x : acc` 有點像 `:` 函數，只是參數順序相反。所以我們可以改成 `foldl (flip (:)) []`。

有個理解摺疊的思路：假設我們有個二元函數 `f`，起始值 `z`，如果從右摺疊 `[3,4,5,6]`，實際上執行的就是 `f 3 (f 4 (f 5 (f 6 z)))`。`f` 會被 List 的尾項和累加值呼叫，所得的結果會作為新的累加值傳入下一個呼叫。假設 `f` 是 `(+)`，起始值 `z` 是 `0`，那麼就是 `3 + (4 + (5 + (6 + 0)))`，或等價的首碼形式：`(+) 3 ((+) 4 ((+) 5 ((+) 6 0)))`。相似，左摺疊一個 List，以 `g` 為二元函數，`z` 為累加值，它就與 `g (g (g (g z 3) 4) 5) 6` 等價。如果用 `flip (:)` 作二元函數，`[]` 為累加值(看得出，我們是要反轉一個 List)，這就與 `flip (:) (flip (:) (flip (:) (flip (:) [] 3) 4) 5) 6` 等價。顯而易見，執行該表達式的結果為 `[6,5,4,3]`。

**scanl** 和 **scanr** 與 `foldl` 和 `foldr` 相似，只是它們會記錄下累加值的所有狀態到一個 List。也有 **scanl1** 和 **scanr1**。

```haskell
ghci> scanl (+) 0 [3,5,2,1]  
[0,3,8,10,11]  
ghci> scanr (+) 0 [3,5,2,1]  
[11,8,3,1,0]  
ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1]  
[3,4,5,5,7,9,9,9]  
ghci> scanl (flip (:)) [] [3,2,1]  
[[],[3],[2,3],[1,2,3]]
```

當使用 `scanl` 時，最終結果就是 List 的最後一個元素。而在 `scanr` 中則是第一個。

```haskell
sqrtSums :: Int  
sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1
```

```haskell
ghci> sqrtSums  
131  
ghci> sum (map sqrt [1..131])  
1005.0942035344083  
ghci> sum (map sqrt [1..130])  
993.6486803921487
```

`scan` 可以用來跟蹤 `fold` 函數的執行過程。想想這個問題，*取所有自然數的平方根的和，尋找在何處超過 1000*？ 先`map sqrt [1..]`，然後用個 `fold` 來求它們的和。但在這裡我們想知道求和的過程，所以使用 `scan`，`scan` 完畢時就可以得到小於 1000 的所有和。所得結果 List 的第一個元素為 1，第二個就是 1+根2，第三個就是 1+根2+根3。若有 `x` 個和小於 1000，那結果就是 `x+1`。

## 有$的函數呼叫

好的，接下來看看 **$** 函數。它也叫作*函數呼叫符*。先看下它的定義：

```haskell
($) :: (a -> b) -> a -> b  
f $ x = f x
```

![](/files/-LjUmtFt_PCJMcSrwM9x)

什麼鬼東西？這沒啥意義的操作符？它只是個函數呼叫符罷了？好吧，不全是，但差不多。普通的函數呼叫符有最高的優先順序，而 `$` 的優先順序則最低。用空格的函數呼叫符是左結合的，如 `f a b c` 與 `((f a) b) c` 等價，而 `$` 則是右結合的。

聽著不錯。但有什麼用？它可以減少我們程式碼中括號的數目。試想有這個表達式： `sum (map sqrt [1..130])`。由於低優先順序的 `$`，我們可以將其改為 `sum $ map sqrt [1..130]`，可以省敲不少鍵！`sqrt 3 + 4 + 9` 會怎樣？這會得到 9，4 和根3 的和。若要取 `(3+4+9)` 的平方根，就得 `sqrt (3+4+9)` 或用 `$`：`sqrt $ 3+4+9`。因為 `$` 有最低的優先順序，所以你可以把$看作是在右面寫一對括號的等價形式。

`sum (filter (> 10) (map (*2) [2..10]))` 該如何？嗯，`$` 是右結合，`f (g (z x))` 與 `f $ g $ z x` 等價。所以我們可以將 `sum (filter (> 10) (map (*2) [2..10])` 重寫為 `sum $ filter (> 10) $ map (*2) [2..10]`。

除了減少括號外，`$` 還可以將數據作為函數使用。例如映射一個函數呼叫符到一組函數組成的 List：

```haskell
ghci> map ($ 3) [(4+),(10*),(^2),sqrt]  
[7.0,30.0,9.0,1.7320508075688772]
```

## Function composition

在數學中，函數組合是這樣定義的： ![](/files/-MSkgjt_nAcYxy2fVvaJ)，表示組合兩個函數成為一個函數。以 `x` 呼叫這一函數，就與用 `x` 呼叫 `g` 再用所得的結果呼叫 `f` 等價。

Haskell 中的函數組合與之很像，即 **.** 函數。其定義為：

```haskell
(.) :: (b -> c) -> (a -> b) -> a -> c  
f . g = \x -> f (g x)
```

![](/files/-MSkgjta8PmDfdsefmB2)

注意下這型別聲明，`f` 的參數型別必須與 `g` 的回傳型別相同。所以得到的組合函數的參數型別與 `g` 相同，回傳型別與 `f` 相同。表達式 `negate . (*3)` 回傳一個求一數字乘以 3 後的負數的函數。

函數組合的用處之一就是生成新函數，並傳遞給其它函數。當然我們可以用 lambda 實現，但大多數情況下，使用函數組合無疑更清楚。假設我們有一組由數字組成的 List，要將其全部轉為負數，很容易就想到應先取其絶對值，再取負數，像這樣：

```haskell
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]
```

注意下這個 lambda 與那函數組合是多麼的相像。用函數組合，我們可以將程式碼改為：

```haskell
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]
```

漂亮！函數組合是右結合的，我們同時組合多個函數。表達式 `f (g (z x))`與 `(f . g . z) x` 等價。按照這個思路，我們可以將

```haskell
ghci> map (\xs -> negate (sum (tail xs))) [[1..5],[3..6],[1..7]]  
[-14,-15,-27]
```

改為：

```haskell
ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]]  
[-14,-15,-27]
```

不過含多個參數的函數該怎麼辦？好，我們可以使用不全呼叫使每個函數都只剩下一個參數。`sum (replicate 5 (max 6.7 8.9))` 可以重寫為 `(sum . replicate 5 . max 6.7) 8.9` 或 `sum . replicate 5 . max 6.7 $ 8.9`。在這裡會產生一個函數，它取與 `max 6.7` 同樣的參數，並使用結果呼叫 `replicate 5` 再用 `sum` 求和。最後用 `8.9` 呼叫該函數。不過一般你可以這麼讀，用 8.9 呼叫 `max 6.7`，然後使它 `replicate 5`，再 `sum` 之。如果你打算用函數組合來替掉那堆括號，可以先在最靠近參數的函數後面加一個 `$`，接着就用 `.` 組合其所有函數呼叫，而不用管最後那個參數。如果有這樣一段程式碼：`replicate 100 (product (map (*3) (zipWith max [1,2,3,4,5] [4,5,6,7,8])))`，可以改為：`replicate 100 . product . map (*3) . zipWith max [1,2,3,4,5] $ [4,5,6,7,8]`。如果表達式以 3 個括號結尾，就表示你可以將其修改為函數組合的形式。

函數組合的另一用途就是定義 point free style (也稱作 pointless style) 的函數。就拿我們之前寫的函數作例子：

```haskell
sum' :: (Num a) => [a] -> a     
sum' xs = foldl (+) 0 xs
```

等號的兩端都有個 `xs`。由於有柯裡化 (Currying)，我們可以省掉兩端的 `xs`。`foldl (+) 0` 回傳的就是一個取一 List 作參數的函數，我們把它修改為 `sum' = foldl (+) 0`，這就是 point free style。下面這個函數又該如何改成 point free style 呢？

```haskell
fn x = ceiling (negate (tan (cos (max 50 x))))
```

像剛才那樣簡單去掉兩端的 `x` 是不行的，函數定義中 `x` 的右邊還有括號。`cos (max 50)` 是有錯誤的，你不能求一個函數的餘弦。我們的解決方法就是，使用函數組合。

```haskell
fn = ceiling . negate . tan . cos . max 50
```

漂亮！point free style 會令你去思考函數的組合方式，而非數據的傳遞方式，更加簡潔明瞭。你可以將一組簡單的函數組合在一起，使之形成一個複雜的函數。不過函數若過于複雜，再使用 point free style 往往會適得其反，因此構造較長的函數組合鏈是不被鼓勵的(雖然我本人熱衷于函數組合)。更好的解決方法，就是使用 `let` 語句給中間的運算結果綁定一個名字，或者說把問題分解成幾個小問題再組合到一起。這樣一來我們程式碼的讀者就可以輕鬆些，不必要糾結那巨長的函數組合鏈了。

在 `map` 和 `filter` 那節中，我們求了小於 10000 的所有奇數的平方的和。如下就是將其置於一個函數中的樣子：

```haskell
oddSquareSum :: Integer  
oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
```

身為函數組合狂人，我可能會這麼寫：

```haskell
oddSquareSum :: Integer  
oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]
```

不過若是給別人看，我可能就這麼寫了：

```haskell
oddSquareSum :: Integer  
oddSquareSum =   
    let oddSquares = filter odd $ map (^2) [1..]  
        belowLimit = takeWhile (<10000) oddSquares  
    in  sum belowLimit
```

這段程式碼可贏不了程式碼花樣大賽，不過我們的讀者可能會覺得它比函數組合鏈更好看。


# ch07


# 模組 (Modules)

## 裝載模組

![](/files/-MhORAy1SOZZr4mQbz5W)

Haskell 中的模組是含有一組相關的函數，型別和型別類的組合。而 Haskell 程序的本質便是從主模組中引用其它模組並呼叫其中的函數來執行操作。這樣可以把程式碼分成多塊，只要一個模組足夠的獨立，它裡面的函數便可以被不同的程序反覆重用。這就讓不同的程式碼各司其職，提高了程式碼的健壯性。

Haskell 的標準庫就是一組模組，每個模組都含有一組功能相近或相關的函數和型別。有處理 List 的模組，有處理並發的模組，也有處理複數的模組，等等。目前為止我們談及的所有函數,型別以及型別類都是 `Prelude` 模組的一部分，它預設自動裝載。在本章，我們看一下幾個常用的模組，在開始瀏覽其中的函數之前，我們先得知道如何裝載模組.

在 Haskell中，裝載模組的語法為 `import`，這必須得在函數的定義之前，所以一般都是將它置於程式碼的頂部。無疑，一段程式碼中可以裝載很多模組，只要將 `import` 語句分行寫開即可。裝載 `Data.List` 試下，它裡面有很多實用的 List 處理函數.

執行 `import Data.List`，這樣一來 `Data.List` 中包含的所有函數就都進入了全局命名空間。也就是說，你可以在程式碼的任意位置呼叫這些函數.`Data.List` 模組中有個 `nub` 函數，它可以篩掉一個 List 中的所有重複元素。用點號將 `length` 和 `nub` 組合: `length . nub`，即可得到一個與 `(\xs -> length (nub xs))` 等價的函數。

```haskell
import Data.List  

numUniques :: (Eq a) => [a] -> Int  
numUniques = length . nub
```

你也可以在 ghci 中裝載模組，若要呼叫 `Data.List` 中的函數，就這樣:

```haskell
ghci> :m Data.List
```

若要在 ghci 中裝載多個模組，不必多次 `:m` 命令，一下就可以全部搞定:

```haskell
ghci> :m Data.List Data.Map Data.Set
```

而你的程序中若已經有包含的程式碼，就不必再用 `:m` 了.

如果你只用得到某模組的兩個函數，大可僅包含它倆。若僅裝載 `Data.List` 模組 `nub` 和 `sort`，就這樣:

```haskell
import Data.List (nub，sort)
```

也可以只包含除去某函數之外的其它函數，這在避免多個模組中函數的命名衝突很有用。假設我們的程式碼中已經有了一個叫做 `nub` 的函數，而裝入 `Data.List` 模組時就要把它裡面的 `nub` 除掉.

```haskell
import Data.List hiding (nub)
```

避免命名衝突還有個方法，便是 `qualified import`，`Data.Map` 模組提供一了一個按鍵索值的資料結構，它裡面有幾個和 `Prelude` 模組重名的函數。如 `filter` 和 `null`，裝入 `Data.Map` 模組之後再呼叫 `filter`，Haskell 就不知道它究竟是哪個函數。如下便是解決的方法:

```haskell
import qualified Data.Map
```

這樣一來，再呼叫 `Data.Map` 中的 `filter` 函數，就必須得 `Data.Map.filter`，而 `filter` 依然是為我們熟悉喜愛的樣子。但是要在每個函數前面都加 `個Data.Map` 實在是太煩人了! 那就給它起個別名，讓它短些:

```haskell
import qualified Data.Map as M
```

好，再呼叫 `Data.Map` 模組的 `filter` 函數的話僅需 `M.filter` 就行了

要瀏覽所有的標準庫模組，參考這個手冊。翻閲標準庫中的模組和函數是提升個人 Haskell 水平的重要途徑。你也可以各個模組的原始碼，這對 Haskell 的深入學習及掌握都是大有好處的.

檢索函數或搜尋函數位置就用 \[<http://www.Haskell.org/hoogle/> Hoogle]，相當了不起的 Haskell 搜索引擎! 你可以用函數名，模組名甚至型別聲明來作為檢索的條件.

## Data.List

顯而易見，`Data.List` 是關於 List 操作的模組，它提供了一組非常有用的 List 處理函數。在前面我們已經見過了其中的幾個函數(如 `map` 和 `filter`)，這是 `Prelude` 模組出於方便起見，導出了幾個 `Data.List` 裡的函數。因為這幾個函數是直接引用自 `Data.List`，所以就無需使用 `qualified import`。在下面，我們來看看幾個以前沒見過的函數:

**intersperse** 取一個元素與 List 作參數，並將該元素置於 List 中每對元素的中間。如下是個例子:

```haskell
ghci> intersperse '.' "MONKEY"  
"M.O.N.K.E.Y"  
ghci> intersperse 0 [1,2,3,4,5,6]  
[1,0,2,0,3,0,4,0,5,0,6]
```

**intercalate** 取兩個 List 作參數。它會將第一個 List 交叉插入第二個 List 中間，並返回一個 List.

```haskell
ghci> intercalate " " ["hey","there","guys"]  
"hey there guys"  
ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]  
[1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]
```

**transpose** 函數可以反轉一組 List 的 List。你若把一組 List 的 List 看作是個 2D 的矩陣，那 `transpose` 的操作就是將其列為行。

```haskell
ghci> transpose [[1,2,3],[4,5,6],[7,8,9]]  
[[1,4,7],[2,5,8],[3,6,9]]  
ghci> transpose ["hey","there","guys"]  
["htg","ehu","yey","rs","e"]
```

假如有兩個多項式 `3x<sup>2</sup> + 5x + 9`，`10x<sup>3</sup> + 9` 和 `8x<sup>3</sup> + 5x<sup>2</sup> + x - 1`，將其相加，我們可以列三個 List: `[0,3,5,9]`，`[10,0,0,9]` 和 `[8,5,1,-1]` 來表示。再用如下的方法取得結果.

```haskell
ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]  
[18,8,6,17]
```

![](/files/-LjUmqcqFqdzx4_iildI)

使用 `transpose` 處理這三個 List 之後，三次冪就到了第一行，二次冪到了第二行，以此類推。在用 `sum` 函數將其映射，即可得到正確的結果。

**foldl'** 和 **foldl1'** 是它們各自惰性實現的嚴格版本。在用 `fold` 處理較大的 List 時，經常會遇到堆棧溢出的問題。而這罪魁禍首就是 `fold` 的惰性: 在執行 `fold` 時，累加器的值並不會被立即更新，而是做一個"在必要時會取得所需的結果"的承諾。每過一遍累加器，這一行為就重複一次。而所有的這堆"承諾"最終就會塞滿你的堆棧。嚴格的 `fold` 就不會有這一問題，它們不會作"承諾"，而是直接計算中間值的結果並繼續執行下去。如果用惰性 `fold` 時經常遇到溢出錯誤，就應換用它們的嚴格版。

**concat** 把一組 List 連接為一個 List。

```haskell
ghci> concat ["foo","bar","car"]  
"foobarcar"  
ghci> concat [[3,4,5],[2,3,4],[2,1,1]]  
[3,4,5,2,3,4,2,1,1]
```

它相當於移除一級嵌套。若要徹底地連接其中的元素，你得 `concat` 它兩次才行.

**concatMap** 函數與 `map` 一個 List 之後再 `concat` 它等價.

```haskell
ghci> concatMap (replicate 4) [1..3]  
[1,1,1,1,2,2,2,2,3,3,3,3]
```

**and** 取一組布林值 List 作參數。只有其中的值全為 `True` 的情況下才會返回 `True`。

```haskell
ghci> and $ map (>4) [5,6,7,8]  
True  
ghci> and $ map (==4) [4,4,4,3,4]  
False
```

**or** 與 `and` 相似，一組布林值 List 中若存在一個 `True` 它就返回 `True`.

```haskell
ghci> or $ map (==4) [2,3,4,5,6,1]  
True  
ghci> or $ map (>4) [1,2,3]  
False
```

**any** 和 **all** 取一個限制條件和一組布林值 List 作參數，檢查是否該 List 的某個元素或每個元素都符合該條件。通常較 `map` 一個 List 到 `and` 或 `or` 而言，使用 `any` 或 `all` 會更多些。

```haskell
ghci> any (==4) [2,3,5,6,1,4]  
True  
ghci> all (>4) [6,9,10]  
True  
ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"  
False  
ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"  
True
```

**iterate** 取一個函數和一個值作參數。它會用該值去呼叫該函數並用所得的結果再次呼叫該函數，產生一個無限的 List.

```haskell
ghci> take 10 $ iterate (*2) 1  
[1,2,4,8,16,32,64,128,256,512]  
ghci> take 3 $ iterate (++ "haha") "haha"  
["haha","hahahaha","hahahahahaha"]
```

**splitAt** 取一個 List 和數值作參數，將該 List 在特定的位置斷開。返回一個包含兩個 List 的二元組.

```haskell
ghci> splitAt 3 "heyman"  
("hey","man")  
ghci> splitAt 100 "heyman"  
("heyman","")  
ghci> splitAt (-3) "heyman"  
("","heyman")  
ghci> let (a,b) = splitAt 3 "foobar" in b ++ a  
"barfoo"
```

**takeWhile** 這一函數十分的實用。它從一個 List 中取元素，一旦遇到不符合條件的某元素就停止.

```haskell
ghci> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]  
[6,5,4]  
ghci> takeWhile (/=' ') "This is a sentence"  
"This"
```

如果要求所有三次方小於 10000 的數的和，用 `filter` 來過濾 `map (^3) [1..]` 所得結果中所有小於 10000 的數是不行的。因為對無限 List 執行的 `filter` 永遠都不會停止。你已經知道了這個 List 是單增的，但 Haskell 不知道。所以應該這樣：

```haskell
ghci> sum $ takeWhile (<10000) $ map (^3) [1..]  
53361
```

用 `(^3)` 處理一個無限 List，而一旦出現了大於等於 10000 的元素這個 List 就被切斷了，sum 到一起也就輕而易舉.

**dropWhile** 與此相似，不過它是扔掉符合條件的元素。一旦限制條件返回 `False`，它就返回 List 的餘下部分。方便實用!

```haskell
ghci> dropWhile (/=' ') "This is a sentence"  
" is a sentence"  
ghci> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1]  
[3,4,5,4,3,2,1]
```

給一 `Tuple` 組成的 List，這 Tuple 的首项表示股票價格，第二三四項分別表示年,月,日。我們想知道它是在哪天首次突破 $1000 的!

```haskell
ghci> let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3,2008,9,5)]  
ghci> head (dropWhile (\(val,y,m,d) -> val < 1000) stock)  
(1001.4,2008,9,4)
```

**span** 與 `takeWhile` 有點像，只是它返回兩個 List。第一個 List 與同參數呼叫 `takeWhile` 所得的結果相同，第二個 List 就是原 List 中餘下的部分。

```haskell
ghci> let (fw，rest) = span (/=' ') "This is a sentence" in "First word:" ++ fw ++ "，the rest:" ++ rest  
"First word: This，the rest: is a sentence"
```

**span** 是在條件首次為 `False` 時斷開 List，而 `break` 則是在條件首次為 `True` 時斷開 `List`。`break p` 與 `span (not . p)` 是等價的.

```haskell
ghci> break (==4) [1,2,3,4,5,6,7]  
([1,2,3],[4,5,6,7])  
ghci> span (/=4) [1,2,3,4,5,6,7]  
([1,2,3],[4,5,6,7])
```

**break** 返回的第二個 List 就會以第一個符合條件的元素開頭。

**sort** 可以排序一個 List，因為只有能夠作比較的元素才可以被排序，所以這一 List 的元素必須是 Ord 型別類的實例型別。

```haskell
ghci> sort [8,5,3,2,1,6,4,2]  
[1,2,2,3,4,5,6,8]  
ghci> sort "This will be sorted soon"  
" Tbdeehiillnooorssstw"
```

**group** 取一個 List 作參數，並將其中相鄰並相等的元素各自歸類，組成一個個子 List.

```haskell
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]  
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
```

若在 `group` 一個 List 之前給它排序就可以得到每個元素在該 List 中的出現次數。

```haskell
ghci> map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]  
[(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]
```

**inits** 和 **tails** 與 `init` 和 `tail` 相似，只是它們會遞歸地呼叫自身直到什麼都不剩，看:

```haskell
ghci> inits "w00t"  
["","w","w0","w00","w00t"]  
ghci> tails "w00t"  
["w00t","00t","0t","t",""]  
ghci> let w = "w00t" in zip (inits w) (tails w)  
[("","w00t"),("w","00t"),("w0","0t"),("w00","t"),("w00t","")]
```

我們用 `fold` 實現一個搜索子 List 的函數:

```haskell
search :: (Eq a) => [a] -> [a] -> Bool  
search needle haystack =  
  let nlen = length needle  
  in foldl (\acc x -> if take nlen x == needle then True else acc) False (tails haystack)
```

首先，對搜索的 List 呼叫 `tails`，然後遍歷每個 List 來檢查它是不是我們想要的.

由此我們便實現了一個類似 **isInfixOf** 的函數，**isInfixOf** 從一個 List 中搜索一個子 List，若該 List 包含子 List，則返回 `True`.

```haskell
ghci> "cat" `isInfixOf` "im a cat burglar"  
True  
ghci> "Cat" `isInfixOf` "im a cat burglar"  
False  
ghci> "cats" `isInfixOf` "im a cat burglar"  
False
```

**isPrefixOf** 與 **isSuffixOf** 分別檢查一個 List 是否以某子 List 開頭或者結尾.

```haskell
ghci> "hey" `isPrefixOf` "hey there!"  
True  
ghci> "hey" `isPrefixOf` "oh hey there!"  
False  
ghci> "there!" `isSuffixOf` "oh hey there!"  
True  
ghci> "there!" `isSuffixOf` "oh hey there"  
False
```

**elem** 與 **notElem** 檢查一個 List 是否包含某元素.

**partition** 取一個限制條件和 List 作參數，返回兩個 List，第一個 List 中包含所有符合條件的元素，而第二個 List 中包含餘下的.

```haskell
ghci> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"  
("BOBMORGAN","sidneyeddy")  
ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7]  
([5,6,7],[1,3,3,2,1,0,3])
```

瞭解這個與 `span` 和 `break` 的差異是很重要的.

```haskell
ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"  
("BOB","sidneyMORGANeddy")
```

`span` 和 `break` 會在遇到第一個符合或不符合條件的元素處斷開，而 `partition` 則會遍歷整個 List。

**find** 取一個 List 和限制條件作參數，並返回首個符合該條件的元素，而這個元素是個 `Maybe` 值。在下章，我們將深入地探討相關的算法和資料結構，但在這裡你只需瞭解 `Maybe` 值是 `Just something` 或 `Nothing` 就夠了。與一個 List 可以為空也可以包含多個元素相似，一個 `Maybe` 可以為空，也可以是單一元素。同樣與 List 類似，一個 Int 型的 List 可以寫作 `[Int]`，`Maybe`有個 Int 型可以寫作 `Maybe Int`。先試一下 `find` 函數再說.

```haskell
ghci> find (>4) [1,2,3,4,5,6]  
Just 5  
ghci> find (>9) [1,2,3,4,5,6]  
Nothing  
ghci> :t find  
find :: (a -> Bool) -> [a] -> Maybe a
```

注意一下 `find` 的型別，它的返回結果為 `Maybe a`，這與 `[a]` 的寫法有點像，只是 `Maybe` 型的值只能為空或者單一元素，而 List 可以為空,一個元素，也可以是多個元素.

想想前面那段找股票的程式碼，`head (dropWhile (\(val,y,m,d) -> val < 1000) stock)` 。但 `head` 並不安全! 如果我們的股票沒漲過 $1000 會怎樣? `dropWhile` 會返回一個空 List，而對空 List 取 `head` 就會引發一個錯誤。把它改成 `find (\(val,y,m,d) -> val > 1000) stock` 就安全多啦，若存在合適的結果就得到它, 像 `Just (1001.4,2008,9,4)`，若不存在合適的元素(即我們的股票沒有漲到過 $1000)，就會得到一個 `Nothing`.

**elemIndex** 與 `elem` 相似，只是它返回的不是布林值，它只是'可能' (Maybe)返回我們找的元素的索引，若這一元素不存在，就返回 `Nothing`。

```haskell
ghci> :t elemIndex  
elemIndex :: (Eq a) => a -> [a] -> Maybe Int  
ghci> 4 `elemIndex` [1,2,3,4,5,6]  
Just 3  
ghci> 10 `elemIndex` [1,2,3,4,5,6]  
Nothing
```

**elemIndices** 與 `elemIndex` 相似，只不過它返回的是 List，就不需要 `Maybe` 了。因為不存在用空 List 就可以表示，這就與 `Nothing` 相似了.

```haskell
ghci> ' ' `elemIndices` "Where are the spaces?"  
[5,9,13]
```

**findIndex** 與 `find` 相似，但它返回的是可能存在的首個符合該條件元素的索引。**findIndices** 會返回所有符合條件的索引.

```haskell
ghci> findIndex (==4) [5,3,2,1,6,4]  
Just 5  
ghci> findIndex (==7) [5,3,2,1,6,4]  
Nothing  
ghci> findIndices (`elem` ['A'..'Z']) "Where Are The Caps?"  
[0,6,10,14]
```

在前面，我們講過了 `zip` 和 `zipWith`，它們只能將兩個 List 組到一個二元組數或二參函數中，但若要組三個 List 該怎麼辦? 好說\~ 有 `zip3`,`zip4`...,和 `zipWith3`, `zipWith4`...直到 7。這看起來像是個 hack，但工作良好。連着組 8 個 List 的情況很少遇到。還有個聰明辦法可以組起無限多個 List，但限於我們目前的水平，就先不談了.

```haskell
ghci> zipWith3 (\x y z -> x + y + z) [1,2,3] [4,5,2,2] [2,2,3]  
[7,9,8]  
ghci> zip4 [2,3,3] [2,2,2] [5,5,3] [2,2,2]  
[(2,2,5,2),(3,2,5,2),(3,2,3,2)]
```

與普通的 `zip` 操作相似，以返回的 List 中長度最短的那個為準.

在處理來自檔案或其它地方的輸入時，**lines** 會非常有用。它取一個字串作參數。並返回由其中的每一行組成的 List.

```haskell
ghci> lines "first line\nsecond line\nthird line"  
["first line","second line","third line"]
```

`'\n'` 表示unix下的換行符，在 Haskell 的字元中，反斜杠表示特殊字元.

**unlines** 是 `lines` 的反函數，它取一組字串的 List，並將其通過 `'\n'`合併到一塊.

```haskell
ghci> unlines ["first line"，"second line"，"third line"]  
"first line\nsecond line\nthird line\n"
```

**words** 和 **unwords** 可以把一個字串分為一組單詞或執行相反的操作，很有用.

```haskell
ghci> words "hey these are the words in this sentence"  
["hey","these","are","the","words","in","this","sentence"]  
ghci> words "hey these are the words in this\nsentence"  
["hey","these","are","the","words","in","this","sentence"]  
ghci> unwords ["hey","there","mate"]  
"hey there mate"
```

我們前面講到了 **nub**，它可以將一個 List 中的重複元素全部篩掉，使該 List 的每個元素都如雪花般獨一無二，'nub' 的含義就是'一小塊'或'一部分'，用在這裡覺得很古怪。我覺得，在函數的命名上應該用更確切的詞語，而避免使用老掉牙的過時詞彙.

```haskell
ghci> nub [1,2,3,4,3,2,1,2,3,4,3,2,1]  
[1,2,3,4]  
ghci> nub "Lots of words and stuff"  
"Lots fwrdanu"
```

**delete** 取一個元素和 List 作參數，會刪掉該 List 中首次出現的這一元素.

```haskell
ghci> delete 'h' "hey there ghang!"  
"ey there ghang!"  
ghci> delete 'h' . delete 'h' $ "hey there ghang!"  
"ey tere ghang!"  
ghci> delete 'h' . delete 'h' . delete 'h' $ "hey there ghang!"  
"ey tere gang!"
```

**\\** 表示 List 的差集操作，這與集合的差集很相似，它會從左邊 List 中的元素扣除存在於右邊 List 中的元素一次.

```haskell
ghci> [1..10] \\ [2,5,9]  
[1,3,4,6,7,8,10]  
ghci> "Im a big baby" \\ "big"  
"Im a  baby"
```

**union** 與集合的並集也是很相似，它返回兩個 List 的並集，即遍歷第二個 List 若存在某元素不屬於第一個 List，則追加到第一個 List。看，第二個 List 中的重複元素就都沒了!

```haskell
ghci> "hey man" `union` "man what's up"  
"hey manwt'sup"  
ghci> [1..7] `union` [5..10]  
[1,2,3,4,5,6,7,8,9,10]
```

**intersect** 相當於集合的交集。它返回兩個 List 的相同部分.

```haskell
ghci> [1..7] `intersect` [5..10]  
[5,6,7]
```

**insert** 可以將一個元素插入一個可排序的 List，並將其置於首個大於等於它的元素之前，如果使用 `insert` 來給一個排過序的 List 插入元素，返回的結果依然是排序的.

```haskell
ghci> insert 4 [1,2,3,5,6,7]  
[1,2,3,4,5,6,7]  
ghci> insert 'g' $ ['a'..'f'] ++ ['h'..'z']  
"abcdefghijklmnopqrstuvwxyz"  
ghci> insert 3 [1,2,4,3,2,1]  
[1,2,3,4,3,2,1]
```

`length`，`take`，`drop`，`splitAt`，`!!` 和 `replicate` 之類的函數有個共同點。那就是它們的參數中都有個 Int 值（或者返回Int值），我覺得使用 Intergal 或 Num 型別類會更好，但出於歷史原因，修改這些會破壞掉許多既有的程式碼。在 `Data.List` 中包含了更通用的替代版，如: `genericLength，genericTake，genericDrop，genericSplitAt，genericIndex` 和 `genericReplicate`。`length` 的型別聲明為 `length :: [a] -> Int`，而我們若要像這樣求它的平均值，`let xs = [1..6] in sum xs / length xs` ，就會得到一個型別錯誤，因為 `/` 運算符不能對 Int 型使用! 而 `genericLength` 的型別聲明則為 `genericLength :: (Num a) => [b] -> a`，Num 既可以是整數又可以是浮點數，`let xs = [1..6] in sum xs / genericLength xs` 這樣再求平均數就不會有問題了.

`nub`, `delete`, `union`, `intsect` 和 `group` 函數也有各自的通用替代版 `nubBy`，`deleteBy`，`unionBy`，`intersectBy` 和 `groupBy`，它們的區別就是前一組函數使用 `(==)` 來測試是否相等，而帶 `By` 的那組則取一個函數作參數來判定相等性，`group` 就與 `groupBy (==)` 等價.

假如有個記錄某函數在每秒的值的 List，而我們要按照它小於零或者大於零的交界處將其分為一組子 List。如果用 `group`，它只能將相鄰並相等的元素組到一起，而在這裡我們的標準是它們是否互爲相反數。`groupBy` 登場! 它取一個含兩個參數的函數作為參數來判定相等性.

```haskell
ghci> let values = [-4.3,-2.4,-1.2,0.4,2.3,5.9,10.5,29.1,5.3,-2.4,-14.5,2.9,2.3]
ghci> groupBy (\x y -> (x > 0) == (y > 0)) values  
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
```

這樣一來我們就可以很清楚地看出哪部分是正數，哪部分是負數，這個判斷相等性的函數會在兩個元素同時大於零或同時小於零時返回 `True`。也可以寫作 `\x y -> (x > 0) && (y > 0) || (x <= 0) && (y <= 0)`。但我覺得第一個寫法的可讀性更高。`Data.Function` 中還有個 `on` 函數可以讓它的表達更清晰，其定義如下:

```haskell
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c  
f `on` g = \x y -> f (g x) (g y)
```

執行 ``(==) `on` (> 0)`` 得到的函數就與 `\x y -> (x > 0) == (y > 0)` 基本等價。`on` 與帶 `By` 的函數在一起會非常好用，你可以這樣寫:

```haskell
ghci> groupBy ((==) `on` (> 0)) values  
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
```

可讀性很高! 你可以大聲念出來: 按照元素是否大於零，給它分類！

同樣，`sort`，`insert`，`maximum` 和 `min` 都有各自的通用版本。如 `groupBy` 類似，**sortBy**，**insertBy**，**maximumBy** 和 **minimumBy** 都取一個函數來比較兩個元素的大小。像 `sortBy` 的型別聲明為: `sortBy :: (a -> a -> Ordering) -> [a] -> [a]`。前面提過，`Ordering` 型別可以有三個值,`LT`，`EQ` 和 `GT`。`compare` 取兩個 `Ord` 型別類的元素作參數，所以 `sort` 與 `sortBy compare` 等價.

List 是可以比較大小的，且比較的依據就是其中元素的大小。如果按照其子 List 的長度為標準當如何? 很好，你可能已經猜到了，`sortBy` 函數.

```haskell
ghci> let xs = [[5,4,5,4,4],[1,2,3],[3,5,4,3],[],[2],[2,2]]  
ghci> sortBy (compare `on` length) xs  
[[],[2],[2,2],[1,2,3],[3,5,4,3],[5,4,5,4,4]]
```

太絶了! ``compare `on` length``，乖乖，這簡直就是英文! 如果你搞不清楚 `on` 在這裡的原理，就可以認為它與 ``\x y -> length x `compare` length y`` 等價。通常，與帶 `By` 的函數打交道時，若要判斷相等性，則 ``(==) `on` something``。若要判定大小，則 ``compare `on` something``.

## Data.Char

如其名，`Data.Char` 模組包含了一組用於處理字元的函數。由於字串的本質就是一組字元的 List，所以往往會在 `filter` 或是 `map` 字串時用到它.

`Data.Char`模組中含有一系列用於判定字元範圍的函數，如下:

![](/files/-MhORAyODEQYuzG1oZS9)

**isControl** 判斷一個字元是否是控制字元。 **isSpace** 判斷一個字元是否是空格字元，包括空格，tab，換行符等. **isLower** 判斷一個字元是否為小寫. **isUper** 判斷一個字元是否為大寫。 **isAlpha** 判斷一個字元是否為字母. **isAlphaNum** 判斷一個字元是否為字母或數字. **isPrint** 判斷一個字元是否是可打印的. **isDigit** 判斷一個字元是否為數字. **isOctDigit** 判斷一個字元是否為八進制數字. **isHexDigit** 判斷一個字元是否為十六進制數字. **isLetter** 判斷一個字元是否為字母. **isMark** 判斷是否為 unicode 注音字元，你如果是法國人就會經常用到的. **isNumber** 判斷一個字元是否為數字. **isPunctuation** 判斷一個字元是否為標點符號. **isSymbol**判斷一個字元是否為貨幣符號. **isSeperater** 判斷一個字元是否為 unicode 空格或分隔符. **isAscii** 判斷一個字元是否在 unicode 字母表的前 128 位。 **isLatin1** 判斷一個字元是否在 unicode 字母表的前 256 位. **isAsciiUpper** 判斷一個字元是否為大寫的 ascii 字元. **isAsciiLower** 判斷一個字元是否為小寫的 ascii 字元.

以上所有判斷函數的型別聲明皆為 `Char -> Bool`，用到它們的絶大多數情況都無非就是過濾字串或類似操作。假設我們在寫個程序，它需要一個由字元和數字組成的用戶名。要實現對用戶名的檢驗，我們可以結合使用 `Data.List` 模組的 `all` 函數與 `Data.Char` 的判斷函數.

```haskell
ghci> all isAlphaNum "bobby283"  
True  
ghci> all isAlphaNum "eddy the fish!"  
False
```

Kewl\~ 免得你忘記，`all` 函數取一個判斷函數和一個 List 做參數，若該 List 的所有元素都符合條件，就返回 `True`.

也可以使用 `isSpace` 來實現 `Data.List` 的 `words` 函數.

```haskell
ghci> words "hey guys its me"  
["hey","guys","its","me"]  
ghci> groupBy ((==) `on` isSpace) "hey guys its me"  
["hey"," ","guys"," ","its"," ","me"]  
ghci>
```

Hmm，不錯，有點 `words` 的樣子了。只是還有空格在裡面，恩，該怎麼辦? 我知道，用 `filter` 濾掉它們!

```haskell
ghci> filter (not . any isSpace) . groupBy ((==) `on` isSpace) $ "hey guys its me"  
["hey","guys","its","me"]
```

啊哈.

`Data.Char` 中也含有與 `Ordering` 相似的型別。`Ordering` 可以有三個值，`LT`，`GT` 和 `EQ`。這就是個枚舉，它表示了兩個元素作比較可能的結果. `GeneralCategory` 型別也是個枚舉，它表示了一個字元可能所在的分類。而得到一個字元所在分類的主要方法就是使用 `generalCategory` 函數.它的型別為: `generalCategory :: Char -> GeneralCategory`。那 31 個分類就不在此一一列出了，試下這個函數先:

```haskell
ghci> generalCategory ' '  
Space  
ghci> generalCategory 'A'  
UppercaseLetter  
ghci> generalCategory 'a'  
LowercaseLetter  
ghci> generalCategory '.'  
OtherPunctuation  
ghci> generalCategory '9'  
DecimalNumber  
ghci> map generalCategory " \t\nA9?|"  
[Space,Control,Control,UppercaseLetter,DecimalNumber,OtherPunctuation,MathSymbol]
```

由於 `GeneralCategory` 型別是 `Eq` 型別類的一部分，使用類似 `generalCategory c == Space` 的程式碼也是可以的.

**toUpper** 將一個字元轉為大寫字母，若該字元不是小寫字母，就按原值返回. **toLower** 將一個字元轉為小寫字母，若該字元不是大寫字母，就按原值返回. **toTitle** 將一個字元轉為 title-case，對大多數字元而言，title-case 就是大寫. **digitToInt** 將一個字元轉為 Int 值，而這一字元必須得在 `'1'..'9','a'..'f'`或`'A'..'F'` 的範圍之內.

```haskell
ghci> map digitToInt "34538"  
[3,4,5,3,8]  
ghci> map digitToInt "FF85AB"  
[15,15,8,5,10,11]
```

`intToDigit` 是 `digitToInt` 的反函數。它取一個 `0` 到 `15` 的 `Int` 值作參數，並返回一個小寫的字元.

```haskell
ghci> intToDigit 15  
'f'  
ghci> intToDigit 5  
'5'
```

**ord** 與 **char** 函數可以將字元與其對應的數字相互轉換.

```haskell
ghci> ord 'a'  
97  
ghci> chr 97  
'a'  
ghci> map ord "abcdefgh"  
[97,98,99,100,101,102,103,104]
```

兩個字元的 `ord` 值之差就是它們在 unicode 字元表上的距離.

*Caesar ciphar* 是加密的基礎算法，它將消息中的每個字元都按照特定的字母表進行替換。它的實現非常簡單，我們這裡就先不管字母表了.

```haskell
encode :: Int -> String -> String  
encode shift msg = 
  let ords = map ord msg  
      shifted = map (+ shift) ords  
  in map chr shifted
```

先將一個字串轉為一組數字，然後給它加上某數，再轉回去。如果你是標準的組合牛仔，大可將函數寫為: `map (chr . (+ shift) . ord) msg`。試一下它的效果:

```haskell
ghci> encode 3 "Heeeeey"  
"Khhhhh|"  
ghci> encode 4 "Heeeeey"  
"Liiiii}"  
ghci> encode 1 "abcd"  
"bcde"  
ghci> encode 5 "Marry Christmas! Ho ho ho!"  
"Rfww~%Hmwnxyrfx&%Mt%mt%mt&"
```

不錯。再簡單地將它轉成一組數字，減去某數後再轉回來就是解密了.

```haskell
decode :: Int -> String -> String  
decode shift msg = encode (negate shift) msg
```

```haskell
ghci> encode 3 "Im a little teapot"  
"Lp#d#olwwoh#whdsrw"  
ghci> decode 3 "Lp#d#olwwoh#whdsrw"  
"Im a little teapot"  
ghci> decode 5 . encode 5 $ "This is a sentence"  
"This is a sentence"
```

## Data.Map

關聯列表(也叫做字典)是按照鍵值對排列而沒有特定順序的一種 List。例如，我們用關聯列表儲存電話號碼，號碼就是值，人名就是鍵。我們並不關心它們的存儲順序，只要能按人名得到正確的號碼就好.在 Haskell 中表示關聯列表的最簡單方法就是弄一個二元組的 List，而這二元組就首項為鍵，後項為值。如下便是個表示電話號碼的關聯列表:

```haskell
phoneBook = [("betty","555-2938") ,
             ("bonnie","452-2928") ,
             ("patsy","493-2928") ,
             ("lucille","205-2928") ,
             ("wendy","939-8282") ,
             ("penny","853-2492") ]
```

不理這貌似古怪的縮進，它就是一組二元組的 List 而已。話說對關聯列表最常見的操作就是按鍵索值，我們就寫個函數來實現它。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> v 
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
```

![](/files/-MhORAyVQsACSx2ULWL9)

簡潔漂亮。這個函數取一個鍵和 List 做參數，過濾這一 List 僅保留鍵匹配的項，並返迴首個鍵值對。但若該關聯列表中不存在這個鍵那會怎樣? 哼，那就會在試圖從空 List 中取 `head` 時引發一個運行時錯誤。無論如何也不能讓程序就這麼輕易地崩潰吧，所以就應該用 `Maybe` 型別。如果沒找到相應的鍵，就返回 `Nothing`。而找到了就返回 `Just something`。而這 `something` 就是鍵對應的值。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v 
findKey key [] = Nothing
findKey key ((k,v):xs) = 
     if key == k then 
         Just v 
     else 
         findKey key xs
```

看這型別聲明，它取一個可判斷相等性的鍵和一個關聯列表做參數，可能 (Maybe) 得到一個值。聽起來不錯.這便是個標準的處理 List 的遞歸函數，邊界條件，分割 List，遞歸呼叫，都有了 -- 經典的 `fold` 模式。 看看用 `fold` 怎樣實現吧。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v 
findKey key = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing
```

```
*Note*: 通常，使用 ``fold`` 來替代類似的遞歸函數會更好些。用 ``fold`` 的程式碼讓人一目瞭然，而看明白遞歸則得多花點腦子。
```

```haskell
ghci> findKey "penny" phoneBook 
Just "853-2492" 
ghci> findKey "betty" phoneBook 
Just "555-2938" 
ghci> findKey "wilma" phoneBook 
Nothing
```

如魔咒般靈驗! 只要我們有這姑娘的號碼就 `Just` 可以得到，否則就是 `Nothing`. 方纔我們實現的函數便是 `Data.List` 模組的 `lookup`，如果要按鍵去尋找相應的值，它就必須得遍歷整個 List，直到找到為止。而 `Data.Map` 模組提供了更高效的方式(通過樹實現)，並提供了一組好用的函數。從現在開始，我們扔掉關聯列表，改用map.由於`Data.Map`中的一些函數與Prelude和`Data.List` 模組存在命名衝突，所以我們使用 `qualified import`。`import qualified Data.Map as Map` 在程式碼中加上這句，並 `load` 到 ghci 中.繼續前進，看看 `Data.Map` 是如何的一座寶庫! 如下便是其中函數的一瞥:

**fromList** 取一個關聯列表，返回一個與之等價的 Map。

```haskell
ghci> Map.fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] 
fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] 
ghci> Map.fromList [(1,2),(3,4),(3,2),(5,5)] 
fromList [(1,2),(3,2),(5,5)]
```

若其中存在重複的鍵,就將其忽略。如下即 `fromList` 的型別聲明。

```haskell
Map.fromList :: (Ord k) => [(k,v)] -> Map.Map k v
```

這表示它取一組鍵值對的 List，並返回一個將 `k` 映射為 `v` 的 `map`。注意一下，當使用普通的關聯列表時，只需要鍵的可判斷相等性就行了。而在這裡，它還必須得是可排序的。這在 `Data.Map` 模組中是強制的。因為它會按照某順序將其組織在一棵樹中.在處理鍵值對時，只要鍵的型別屬於 `Ord` 型別類，就應該儘量使用`Data.Map`.`empty` 返回一個空 `map`.

```haskell
ghci> Map.empty 
fromList []
```

**insert** 取一個鍵，一個值和一個 `map` 做參數，給這個 `map` 插入新的鍵值對，並返回一個新的 `map`。

```haskell
ghci> Map.empty 
fromList [] 
ghci> Map.insert 3 100 Map.empty
fromList [(3,100)] 
ghci> Map.insert 5 600 (Map.insert 4 200 ( Map.insert 3 100  Map.empty)) 
fromList [(3,100),(4,200),(5,600)]
ghci> Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty 
fromList [(3,100),(4,200),(5,600)]
```

通過 `empty`，`insert` 與 `fold`，我們可以編寫出自己的 `fromList`。

```haskell
fromList' :: (Ord k) => [(k,v)] -> Map.Map k v 
fromList' = foldr (\(k,v) acc -> Map.insert k v acc) Map.empty
```

簡潔明瞭的 `fold`！ 從一個空的 `map` 開始，然後從右摺疊，隨着遍歷不斷地往 `map` 中插入新的鍵值對.

**null** 檢查一個 `map` 是否為空.

```haskell
ghci> Map.null Map.empty 
True 
ghci> Map.null $ Map.fromList [(2,3),(5,5)] 
False
```

**size** 返回一個 `map` 的大小。

```haskell
ghci> Map.size Map.empty 
0 
ghci> Map.size $ Map.fromList [(2,4),(3,3),(4,2),(5,4),(6,4)] 
5
```

**singleton** 取一個鍵值對做參數,並返回一個只含有一個映射的 `map`.

```haskell
ghci> Map.singleton 3 9 
fromList [(3,9)] 
ghci> Map.insert 5 9 $ Map.singleton 3 9 
fromList [(3,9),(5,9)]
```

**lookup** 與 `Data.List` 的 `lookup` 很像,只是它的作用對象是 `map`，如果它找到鍵對應的值。就返回 `Just something`，否則返回 `Nothing`。

**member** 是個判斷函數，它取一個鍵與 `map` 做參數，並返回該鍵是否存在於該 `map`。

```haskell
ghci> Map.member 3 $ Map.fromList [(3,6),(4,3),(6,9)] 
True 
ghci> Map.member 3 $ Map.fromList [(2,5),(4,5)] 
False
```

**map** 與 **filter** 與其對應的 `List` 版本很相似:

```haskell
ghci> Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)] 
fromList [(1,100),(2,400),(3,900)] 
ghci> Map.filter isUpper $ Map.fromList [(1,'a'),(2,'A'),(3,'b'),(4,'B')] 
fromList [(2,'A'),(4,'B')]
```

`toList` 是 `fromList` 的反函數。

```haskell
ghci> Map.toList . Map.insert 9 2 $ Map.singleton 4 3 
[(4,3),(9,2)]
```

**keys** 與 **elems** 各自返回一組由鍵或值組成的 List，`keys` 與 `map fst . Map.toList` 等價，`elems` 與 `map snd . Map.toList`等價. `fromListWith` 是個很酷的小函數，它與 `fromList` 很像，只是它不會直接忽略掉重複鍵，而是交給一個函數來處理它們。假設一個姑娘可以有多個號碼，而我們有個像這樣的關聯列表:

```haskell
phoneBook =   
    [("betty","555-2938")  
    ,("betty","342-2492")  
    ,("bonnie","452-2928")  
    ,("patsy","493-2928")  
    ,("patsy","943-2929")  
    ,("patsy","827-9162")  
    ,("lucille","205-2928")  
    ,("wendy","939-8282")  
    ,("penny","853-2492")  
    ,("penny","555-2111")  
    ]
```

如果用 `fromList` 來生成 `map`，我們會丟掉許多號碼! 如下才是正確的做法:

```haskell
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String  
phoneBookToMap xs = Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) xs
```

```haskell
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook 
"827-9162, 943-2929, 493-2928" 
ghci> Map.lookup "wendy" $ phoneBookToMap phoneBook
"939-8282" 
ghci> Map.lookup "betty" $ phoneBookToMap phoneBook 
"342-2492,555-2938"
```

一旦出現重複鍵，這個函數會將不同的值組在一起，同樣，也可以預設地將每個值放到一個單元素的 List 中，再用 `++` 將他們都連接在一起。

```haskell
phoneBookToMap :: (Ord k) => [(k,a)] -> Map.Map k [a] 
phoneBookToMap xs = Map.fromListWith (++) $ map (\(k,v) -> (k,[v])) xs 
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook 
["827-9162","943-2929","493-2928"]
```

很簡潔! 它還有別的玩法，例如在遇到重複元素時，單選最大的那個值.

```haskell
ghci> Map.fromListWith max [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)] 
fromList [(2,100),(3,29),(4,22)]
```

或是將相同鍵的值都加在一起.

```haskell
ghci> Map.fromListWith (+) [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)] 
fromList [(2,108),(3,62),(4,37)]
```

**insertWith** 之於 `insert`，恰如 `fromListWith` 之於 `fromList`。它會將一個鍵值對插入一個 `map` 之中，而該 `map` 若已經包含這個鍵，就問問這個函數該怎麼辦。

```haskell
ghci> Map.insertWith (+) 3 100 $ Map.fromList [(3,4),(5,103),(6,339)] 
fromList [(3,104),(5,103),(6,339)]
```

`Data.Map` 裡面還有不少函數，\[<http://www.haskell.org/ghc/docs/latest/html/libraries/containers/Data-Map.html> 這個文檔]中的列表就很全了.

## Data.Set

![](/files/-MhORAyfewG29ZDpZtSc)

`Data.Set` 模組提供了對數學中集合的處理。集合既像 List 也像 `Map`: 它裡面的每個元素都是唯一的，且內部的數據由一棵樹來組織(這和 `Data.Map` 模組的 `map` 很像)，必須得是可排序的。同樣是插入,刪除,判斷從屬關係之類的操作，使用集合要比 List 快得多。對一個集合而言，最常見的操作莫過于並集，判斷從屬或是將集合轉為 List.

由於 `Data.Set` 模組與 `Prelude` 模組和 `Data.List` 模組中存在大量的命名衝突，所以我們使用 `qualified import`

將 `import` 語句至于程式碼之中:

```haskell
import qualified Data.Set as Set
```

然後在 ghci 中裝載

假定我們有兩個字串，要找出同時存在於兩個字串的字元

```haskell
text1 = "I just had an anime dream. Anime... Reality... Are they so different?"  
text2 = "The old man left his garbage can out and now his trash is all over my lawn!"
```

**fromList** 函數同你想的一樣，它取一個 List 作參數並將其轉為一個集合

```haskell
ghci> let set1 = Set.fromList text1  
ghci> let set2 = Set.fromList text2  
ghci> set1  
fromList " .?AIRadefhijlmnorstuy"  
ghci> set2  
fromList " !Tabcdefghilmnorstuvwy"
```

如你所見，所有的元素都被排了序。而且每個元素都是唯一的。現在我們取它的交集看看它們共同包含的元素:

```haskell
ghci> Set.intersection set1 set2  
fromList " adefhilmnorstuy"
```

使用 `difference` 函數可以得到存在於第一個集合但不在第二個集合的元素

```haskell
ghci> Set.difference set1 set2  
fromList ".?AIRj"  
ghci> Set.difference set2 set1  
fromList "!Tbcgvw"
```

也可以使用 `union` 得到兩個集合的並集

```haskell
ghci> Set.union set1 set2  
fromList " !.?AIRTabcdefghijlmnorstuvwy"
```

`null`，`size`，`member`，`empty`，`singleton`，`insert`，`delete` 這幾個函數就跟你想的差不多啦

```haskell
ghci> Set.null Set.empty  
True  
ghci> Set.null $ Set.fromList [3,4,5,5,4,3]  
False  
ghci> Set.size $ Set.fromList [3,4,5,3,4,5]  
3  
ghci> Set.singleton 9  
fromList [9]  
ghci> Set.insert 4 $ Set.fromList [9,3,8,1]  
fromList [1,3,4,8,9]  
ghci> Set.insert 8 $ Set.fromList [5..10]  
fromList [5,6,7,8,9,10]  
ghci> Set.delete 4 $ Set.fromList [3,4,5,4,3,4,5]  
fromList [3,5]
```

也可以判斷子集與真子集，如果集合 A 中的元素都屬於集合 B，那麼 A 就是 B 的子集, 如果 A 中的元素都屬於 B 且 B 的元素比 A 多，那 A 就是 B 的真子集

```haskell
ghci> Set.fromList [2,3,4] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
True  
ghci> Set.fromList [1,2,3,4,5] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
True  
ghci> Set.fromList [1,2,3,4,5] `Set.isProperSubsetOf` Set.fromList [1,2,3,4,5]  
False  
ghci> Set.fromList [2,3,4,8] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
False
```

對集合也可以執行 `map` 和 `filter`:

```haskell
ghci> Set.filter odd $ Set.fromList [3,4,5,6,7,2,3,4]  
fromList [3,5,7]  
ghci> Set.map (+1) $ Set.fromList [3,4,5,6,7,2,3,4]  
fromList [3,4,5,6,7,8]
```

集合有一常見用途，那就是先 `fromList` 刪掉重複元素後再 `toList` 轉回去。儘管 `Data.List` 模組的 `nub` 函數完全可以完成這一工作，但在對付大 List 時則會明顯的力不從心。使用集合則會快很多，`nub` 函數只需 List 中的元素屬於 `Eq` 型別類就行了，而若要使用集合，它必須得屬於 `Ord` 型別類

```haskell
ghci> let setNub xs = Set.toList $ Set.fromList xs  
ghci> setNub "HEY WHATS CRACKALACKIN"  
" ACEHIKLNRSTWY"  
ghci> nub "HEY WHATS CRACKALACKIN"  
"HEY WATSCRKLIN"
```

在處理較大的 List 時，`setNub` 要比 `nub` 快，但也可以從中看出，`nub` 保留了 List 中元素的原有順序，而 `setNub` 不。

## 建立自己的模組

我們已經見識過了幾個很酷的模組，但怎樣才能構造自己的模組呢? 几乎所有的編程語言都允許你將程式碼分成多個檔案，Haskell 也不例外。在編程時，將功能相近的函數和型別至于同一模組中會是個很好的習慣。這樣一來，你就可以輕鬆地一個 `import` 來重用其中的函數.

接下來我們將構造一個由計算機幾何圖形體積和面積組成的模組，先從新建一個 `Geometry.hs` 的檔案開始.

在模組的開頭定義模組的名稱，如果檔案名叫做 `Geometry.hs` 那它的名字就得是 `Geometry`。在聲明出它含有的函數名之後就可以編寫函數的實現啦，就這樣寫:

```haskell
module Geometry  
( sphereVolume  
，sphereArea  
，cubeVolume  
，cubeArea  
，cuboidArea  
，cuboidVolume  
) where
```

如你所見，我們提供了對球體,立方體和立方體的面積和體積的解法。繼續進發，定義函數體:

```haskell
module Geometry  
( sphereVolume  
，sphereArea  
，cubeVolume  
，cubeArea  
，cuboidArea  
，cuboidVolume  
) where  

sphereVolume :: Float -> Float  
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)  

sphereArea :: Float -> Float  
sphereArea radius = 4 * pi * (radius ^ 2)  

cubeVolume :: Float -> Float  
cubeVolume side = cuboidVolume side side side  

cubeArea :: Float -> Float  
cubeArea side = cuboidArea side side side  

cuboidVolume :: Float -> Float -> Float -> Float  
cuboidVolume a b c = rectangleArea a b * c  

cuboidArea :: Float -> Float -> Float -> Float  
cuboidArea a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2  

rectangleArea :: Float -> Float -> Float  
rectangleArea a b = a * b
```

![](/files/-MhORAyq2mGVBtbVpioW)

標準的幾何公式。有幾個地方需要注意一下，由於立方體只是長方體的特殊形式，所以在求它面積和體積的時候我們就將它當作是邊長相等的長方體。在這裡還定義了一個 `helper`函數，`rectangleArea` 它可以通過長方體的兩條邊計算出長方體的面積。它僅僅是簡單的相乘而已，份量不大。但請注意我們可以在這一模組中呼叫這個函數，而它不會被導出! 因為我們這個模組只與三維圖形打交道.

當構造一個模組的時候，我們通常只會導出那些行為相近的函數，而其內部的實現則是隱蔽的。如果有人用到了 `Geometry` 模組，就不需要關心它的內部實現是如何。我們作為編寫者，完全可以隨意修改這些函數甚至將其刪掉，沒有人會注意到裡面的變動，因為我們並不把它們導出.

要使用我們的模組，只需:

```haskell
import Geometry
```

將 `Geometry.hs` 檔案至于用到它的程序檔案的同一目錄之下.

模組也可以按照分層的結構來組織，每個模組都可以含有多個子模組。而子模組還可以有自己的子模組。我們可以把 `Geometry` 分成三個子模組，而一個模組對應各自的圖形對象.

首先，建立一個 `Geometry` 檔案夾，注意首字母要大寫，在裡面新建三個檔案

如下就是各個檔案的內容:

sphere.hs

```haskell
module Geometry.Sphere  
( volume  
，area  
) where  

volume :: Float -> Float  
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)  

area :: Float -> Float  
area radius = 4 * pi * (radius ^ 2)
```

cuboid.hs

```haskell
module Geometry.Cuboid  
( volume  
，area  
) where  

volume :: Float -> Float -> Float -> Float  
volume a b c = rectangleArea a b * c  

area :: Float -> Float -> Float -> Float  
area a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2  

rectangleArea :: Float -> Float -> Float  
rectangleArea a b = a * b
```

cube.hs

```haskell
module Geometry.Cube  
( volume  
，area  
) where  

import qualified Geometry.Cuboid as Cuboid  

volume :: Float -> Float  
volume side = Cuboid.volume side side side  

area :: Float -> Float  
area side = Cuboid.area side side side
```

好的! 先是 `Geometry.Sphere`。注意，我們將它置於 `Geometry` 檔案夾之中並將它的名字定為 `Geometry.Sphere`。對 Cuboid 也是同樣，也注意下，在三個模組中我們定義了許多名稱相同的函數，因為所在模組不同，所以不會產生命名衝突。若要在 `Geometry.Cube` 使用 `Geometry.Cuboid` 中的函數，就不能直接 `import Geometry.Cuboid`，而必須得 `qualified import`。因為它們中間的函數名完全相同.

```haskell
import Geometry.Sphere
```

然後，呼叫 `area` 和 `volume`，就可以得到球體的面積和體積，而若要用到兩個或更多此類模組，就必須得 `qualified import` 來避免重名。所以就得這樣寫:

```haskell
import qualified Geometry.Sphere as Sphere  
import qualified Geometry.Cuboid as Cuboid  
import qualified Geometry.Cube as Cube
```

然後就可以呼叫 `Sphere.area`，`Sphere.volume`，`Cuboid.area` 了，而每個函數都只計算其對應物體的面積和體積.

以後你若發現自己的程式碼體積龐大且函數眾多，就應該試着找找目的相近的函數能否裝入各自的模組，也方便日後的重用.


# ch08


# 構造我們自己的 Types 和 Typeclasses

## Algebraic Data Types 入門

在前面的章節中，我們談了一些 Haskell 內建的型別和 Typeclass。而在本章中，我們將學習構造型別和 Typeclass 的方法。

我們已經見識過許多型別，如 `Bool`、`Int`、`Char`、`Maybe` 等等，不過在 Haskell 中該如何構造自己的型別呢？好問題，一種方法是使用 *data* 關鍵字。首先我們來看看 `Bool` 在標準函式庫中的定義：

```haskell
data Bool = False | True
```

*data* 表示我們要定義一個新的型別。`=` 的左端標明型別的名稱即 `Bool`，`=` 的右端就是*值構造子* (*Value Constructor*)，它們明確了該型別可能的值。`|` 讀作"或"，所以可以這樣閲讀該聲明：`Bool` 型別的值可以是 `True` 或 `False`。型別名和值構造子的首字母必大寫。

相似，我們可以假想 `Int` 型別的聲明：

```haskell
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
```

![](/files/-LjUmqjCQ2aSWF--t_lG)

頭尾兩個值構造子分別表示了 `Int` 型別的最小值和最大值，注意到真正的型別宣告不是長這個樣子的，這樣寫只是為了便於理解。我們用省略號表示中間省略的一大段數字。

我們想想 Haskell 中圖形的表示方法。表示圓可以用一個 Tuple，如 `(43.1,55.0,10.4)`，前兩項表示圓心的位置，末項表示半徑。聽著不錯，不過三維向量或其它什麼東西也可能是這種形式！更好的方法就是自己構造一個表示圖形的型別。假定圖形可以是圓 (Circle) 或長方形 (Rectangle)：

```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
```

這是啥，想想？`Circle` 的值構造子有三個項，都是 Float。可見我們在定義值構造子時，可以在後面跟幾個型別表示它包含值的型別。在這裡，前兩項表示圓心的坐標，尾項表示半徑。`Rectangle` 的值構造子取四個 `Float` 項，前兩項表示其左上角的坐標，後兩項表示右下角的坐標。

談到「項」 (field)，其實應為「參數」 (parameters)。值構造子的本質是個函數，可以返回一個型別的值。我們看下這兩個值構造子的型別聲明：

```haskell
ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape
ghci> :t Rectangle
Rectangle :: Float -> Float -> Float -> Float -> Shape
```

Cool，這麼說值構造子就跟普通函數並無二致囉，誰想得到？我們寫個函數計算圖形面積：

```haskell
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

值得一提的是，它的型別聲明表示了該函數取一個 `Shape` 值並返回一個 `Float` 值。寫 `Circle -> Float` 是不可以的，因為 `Circle` 並非型別，真正的型別應該是 `Shape`。這與不能寫`True->False` 的道理是一樣的。再就是，我們使用的模式匹配針對的都是值構造子。之前我們匹配過 `[]`、`False` 或 `5`，它們都是不包含參數的值構造子。

我們只關心圓的半徑，因此不需理會表示坐標的前兩項：

```haskell
ghci> surface $ Circle 10 20 10
314.15927
ghci> surface $ Rectangle 0 0 100 100
10000.0
```

Yay，it works！不過我們若嘗試輸出 `Circle 10 20` 到控制台，就會得到一個錯誤。這是因為 Haskell 還不知道該型別的字元串表示方法。想想，當我們往控制台輸出值的時候，Haskell 會先呼叫 `show` 函數得到這個值的字元串表示才會輸出。因此要讓我們的 `Shape` 型別成為 Show 型別類的成員。可以這樣修改：

```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
```

先不去深究 *deriving*（派生），可以先這樣理解：若在 `data` 聲明的後面加上 `deriving (Show)`，那 Haskell 就會自動將該型別至于 `Show` 型別類之中。好了，由於值構造子是個函數，因此我們可以拿它交給 `map`，拿它不全呼叫，以及普通函數能做的一切。

```haskell
ghci> Circle 10 20 5
Circle 10.0 20.0 5.0
ghci> Rectangle 50 230 60 90
Rectangle 50.0 230.0 60.0 90.0
```

我們若要取一組不同半徑的同心圓，可以這樣：

```haskell
ghci> map (Circle 10 20) [4,5,6,6]
[Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]
```

我們的型別還可以更好。增加加一個表示二維空間中點的型別，可以讓我們的 `Shape` 更加容易理解：

```haskell
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
```

注意下 `Point` 的定義，它的型別與值構造子用了相同的名字。沒啥特殊含義，實際上，在一個型別含有唯一值構造子時這種重名是很常見的。好的，如今我們的 `Circle` 含有兩個項，一個是 `Point` 型別，一個是 `Float` 型別，好作區分。`Rectangle` 也是同樣，我們得修改 `surface` 函數以適應型別定義的變動。

```haskell
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

唯一需要修改的地方就是模式。在 `Circle` 的模式中，我們無視了整個 `Point`。而在 `Rectangle` 的模式中，我們用了一個嵌套的模式來取得 `Point` 中的項。若出於某原因而需要整個 `Point`，那麼直接匹配就是了。

```haskell
ghci> surface (Rectangle (Point 0 0) (Point 100 100))
10000.0
ghci> surface (Circle (Point 0 0) 24)
1809.5574
```

表示移動一個圖形的函數該怎麼寫？它應當取一個 `Shape` 和表示位移的兩個數，返回一個位於新位置的圖形。

```haskell
nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))
```

簡潔明瞭。我們再給這一 `Shape` 的點加上位移的量。

```haskell
ghci> nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
```

如果不想直接處理 `Point`，我們可以搞個輔助函數 (auxilliary function)，初始從原點創建圖形，再移動它們。

```haskell
baseCircle :: Float -> Shape
baseCircle r = Circle (Point 0 0) r

baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
```

```haskell
ghci> nudge (baseRect 40 100) 60 23
Rectangle (Point 60.0 23.0) (Point 100.0 123.0)
```

毫無疑問，你可以把你的數據型別導出到模組中。只要把你的型別與要導出的函數寫到一起就是了。再在後面跟個括號，列出要導出的值構造子，用逗號隔開。如要導出所有的值構造子，那就寫個..。

若要將這裡定義的所有函數和型別都導出到一個模組中，可以這樣：

```haskell
module Shapes
( Point(..)
, Shape(..)
, surface
, nudge
, baseCircle
, baseRect
) where
```

一個 `Shape` (..)，我們就導出了 `Shape` 的所有值構造子。這一來無論誰導入我們的模組，都可以用 `Rectangle` 和 `Circle` 值構造子來構造 `Shape` 了。這與寫 `Shape(Rectangle,Circle)` 等價。

我們可以選擇不導出任何 `Shape` 的值構造子，這一來使用我們模組的人就只能用輔助函數 `baseCircle` 和 `baseRect` 來得到 `Shape` 了。`Data.Map` 就是這一套，沒有 `Map.Map [(1,2),(3,4)]`，因為它沒有導出任何一個值構造子。但你可以用，像 `Map.fromList` 這樣的輔助函數得到 `map`。應該記住，值構造子只是函數而已，如果不導出它們，就拒絶了使用我們模組的人呼叫它們。但可以使用其他返回該型別的函數，來取得這一型別的值。

不導出數據型別的值構造子隱藏了他們的內部實現，令型別的抽象度更高。同時，我們模組的使用者也就無法使用該值構造子進行模式匹配了。

## Record Syntax

OK，我們需要一個數據型別來描述一個人，得包含他的姓、名、年齡、身高、電話號碼以及最愛的冰淇淋。我不知你的想法，不過我覺得要瞭解一個人，這些資料就夠了。就這樣，實現出來！

```haskell
data Person = Person String String Int Float String String deriving (Show)
```

O\~Kay，第一項是名，第二項是姓，第三項是年齡，等等。我們造一個人：

```haskell
ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
ghci> guy
Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
```

貌似很酷，就是難讀了點兒。弄個函數得人的某項資料又該如何？如姓的函數，名的函數，等等。好吧，我們只能這樣：

```haskell
firstName :: Person -> String
firstName (Person firstname _ _ _ _ _) = firstname

lastName :: Person -> String
lastName (Person _ lastname _ _ _ _) = lastname

age :: Person -> Int
age (Person _ _ age _ _ _) = age

height :: Person -> Float
height (Person _ _ _ height _ _) = height

phoneNumber :: Person -> String
phoneNumber (Person _ _ _ _ number _) = number

flavor :: Person -> String
flavor (Person _ _ _ _ _ flavor) = flavor
```

唔，我可不願寫這樣的程式碼！雖然 it works，但也太無聊了哇。

```haskell
ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
ghci> firstName guy
"Buddy"
ghci> height guy
184.2
ghci> flavor guy
"Chocolate"
```

你可能會說，一定有更好的方法！呃，抱歉，沒有。

開個玩笑，其實有的，哈哈哈～Haskell 的發明者都是天才，早就料到了此類情形。他們引入了一個特殊的型別，也就是剛纔提到的更好的方法 -- *Record Syntax*。

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     , height :: Float
                     , phoneNumber :: String
                     , flavor :: String
                     } deriving (Show)
```

與原先讓那些項一個挨一個的空格隔開不同，這裡用了花括號 `{}`。先寫出項的名字，如 `firstName`，後跟兩個冒號(也叫 Paamayim Nekudotayim，哈哈\~(譯者不知道什麼意思\~囧))，標明其型別，返回的數據型別仍與以前相同。這樣的好處就是，可以用函數從中直接按項取值。通過 Record Syntax，Haskell 就自動生成了這些函數：`firstName`, `lastName`, `age`, `height`, `phoneNumber` 和 `flavor`。

```haskell
ghci> :t flavor
flavor :: Person -> String
ghci> :t firstName
firstName :: Person -> String
```

還有個好處，就是若派生 (deriving) 到 `Show` 型別類，它的顯示是不同的。假如我們有個型別表示一輛車，要包含生產商、型號以及出場年份：

```haskell
data Car = Car String String Int deriving (Show)
```

```haskell
ghci> Car "Ford" "Mustang" 1967
Car "Ford" "Mustang" 1967
```

若用 Record Syntax，就可以得到像這樣的新車：

```haskell
data Car = Car {company :: String, model :: String, year :: Int} deriving (Show)
```

```haskell
ghci> Car {company="Ford", model="Mustang", year=1967}
Car {company = "Ford", model = "Mustang", year = 1967}
```

這一來在造車時我們就不必關心各項的順序了。

表示三維向量之類簡單數據，`Vector = Vector Int Int Int` 就足夠明白了。但一個值構造子中若含有很多個項且不易區分，如一個人或者一輛車啥的，就應該使用 Record Syntax。

## Type parameters

值構造子可以取幾個參數產生一個新值，如 `Car` 的構造子是取三個參數返回一個 `Car`。與之相似，型別構造子可以取型別作參數，產生新的型別。這咋一聽貌似有點深奧，不過實際上並不複雜。如果你對 C++ 的模板有瞭解，就會看到很多相似的地方。我們看一個熟悉的型別，好對型別參數有個大致印象：

```haskell
data Maybe a = Nothing | Just a
```

![](/files/-LjUmqjfFfuOmW5El_-S)

這裡的a就是個型別參數。也正因為有了它，`Maybe` 就成為了一個型別構造子。在它的值不是 `Nothing` 時，它的型別構造子可以搞出 `Maybe Int`，`Maybe String` 等等諸多型別。但只一個 `Maybe` 是不行的，因為它不是型別，而是型別構造子。要成為真正的型別，必須得把它需要的型別參數全部填滿。

所以，如果拿 `Char` 作參數交給 `Maybe`，就可以得到一個 `Maybe Char` 的型別。如，`Just 'a'` 的型別就是 `Maybe Char` 。

你可能並未察覺，在遇見 Maybe 之前我們早就接觸到型別參數了。它便是 List 型別。這裡面有點語法糖，List 型別實際上就是取一個參數來生成一個特定型別，這型別可以是 `[Int]`，`[Char]` 也可以是 `[String]`，但不會跟在 `[]` 的後面。

把玩一下 `Maybe`！

```haskell
ghci> Just "Haha"
Just "Haha"
ghci> Just 84
Just 84
ghci> :t Just "Haha"
Just "Haha" :: Maybe [Char]
ghci> :t Just 84
Just 84 :: (Num t) => Maybe t
ghci> :t Nothing
Nothing :: Maybe a
ghci> Just 10 :: Maybe Double
Just 10.0
```

型別參數很實用。有了它，我們就可以按照我們的需要構造出不同的型別。若執行 `:t Just "Haha"`，型別推導引擎就會認出它是個 `Maybe [Char]`，由於 `Just a` 裡的 `a` 是個字元串，那麼 `Maybe a` 裡的 `a` 一定也是個字元串。

![](/files/-LjUmqjh294K_0sx5ex6)

注意下，`Nothing` 的型別為 `Maybe a`。它是多態的，若有函數取 `Maybe Int` 型別的參數，就一概可以傳給它一個 `Nothing`，因為 `Nothing` 中不包含任何值。`Maybe a` 型別可以有 `Maybe Int` 的行為，正如 `5` 可以是 `Int` 也可以是 `Double`。與之相似，空 List 的型別是 `[a]`，可以與一切 List 打交道。因此，我們可以 `[1,2,3]++[]`，也可以 `["ha","ha,","ha"]++[]`。

型別參數有很多好處，但前提是用對了地方才行。一般都是不關心型別裡面的內容，如 `Maybe a`。一個型別的行為若有點像是容器，那麼使用型別參數會是個不錯的選擇。我們完全可以把我們的`Car`型別從

```haskell
data Car = Car { company :: String
                 , model :: String
                 , year :: Int
                 } deriving (Show)
```

改成：

```haskell
data Car a b c = Car { company :: a
                       , model :: b
                       , year :: c
                        } deriving (Show)
```

但是，這樣我們又得到了什麼好處？回答很可能是，一無所得。因為我們只定義了處理 `Car String String Int` 型別的函數，像以前，我們還可以弄個簡單函數來描述車的屬性。

```haskell
tellCar :: Car -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
```

```haskell
ghci> let stang = Car {company="Ford", model="Mustang", year=1967}
ghci> tellCar stang
"This Ford Mustang was made in 1967"
```

可愛的小函數！它的型別聲明得很漂亮，而且工作良好。好，如果改成 `Car a b c` 又會怎樣？

```haskell
tellCar :: (Show a) => Car String String a -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
```

我們只能強制性地給這個函數安一個 `(Show a) => Car String String a` 的型別約束。看得出來，這要繁複得多。而唯一的好處貌似就是，我們可以使用 `Show` 型別類的 `instance` 來作 `a` 的型別。

```haskell
ghci> tellCar (Car "Ford" "Mustang" 1967)
"This Ford Mustang was made in 1967"
ghci> tellCar (Car "Ford" "Mustang" "nineteen sixty seven")
"This Ford Mustang was made in \"nineteen sixty seven\""
ghci> :t Car "Ford" "Mustang" 1967
Car "Ford" "Mustang" 1967 :: (Num t) => Car [Char] [Char] t
ghci> :t Car "Ford" "Mustang" "nineteen sixty seven"
Car "Ford" "Mustang" "nineteen sixty seven" :: Car [Char] [Char] [Char]
```

其實在現實生活中，使用 `Car String String Int` 在大多數情況下已經滿夠了。所以給 `Car` 型別加型別參數貌似並沒有什麼必要。通常我們都是都是在一個型別中包含的型別並不影響它的行為時才引入型別參數。一組什麼東西組成的 List 就是一個 List，它不關心裡面東西的型別是啥，然而總是工作良好。若取一組數字的和，我們可以在後面的函數體中明確是一組數字的 List。Maybe 與之相似，它表示可以有什麼東西可以沒有，而不必關心這東西是啥。

我們之前還遇見過一個型別參數的應用，就是 `Data.Map` 中的 `Map k v`。 `k` 表示 Map 中鍵的型別，`v` 表示值的型別。這是個好例子，Map 中型別參數的使用允許我們能夠用一個型別索引另一個型別，只要鍵的型別在 `Ord` 型別類就行。如果叫我們自己定義一個 Map 型別，可以在 `data` 聲明中加上一個型別類的約束。

```haskell
data (Ord k) => Map k v = ...
```

然而 Haskell 中有一個嚴格的約定，那就是永遠不要在 `data` 聲明中添加型別約束。為啥？嗯，因為這樣沒好處，反而得寫更多不必要的型別約束。`Map k v` 要是有 `Ord k` 的約束，那就相當於假定每個 Map 的相關函數都認為 `k` 是可排序的。若不給數據型別加約束，我們就不必給那些不關心鍵是否可排序的函數另加約束了。這類函數的一個例子就是 `toList`，它只是把一個 Map 轉換為關聯 List 罷了，型別聲明為 `toList :: Map k v -> [(k, v)]`。要是加上型別約束，就只能是 `toList :: (Ord k) =>Map k a -> [(k,v)]`，明顯沒必要嘛。

所以說，永遠不要在 `data` 聲明中加型別約束 --- 即便看起來沒問題。免得在函數聲明中寫出過多無謂的型別約束。

我們實現個表示三維向量的型別，再給它加幾個處理函數。我麼那就給它個型別參數，雖然大多數情況都是數值型，不過這一來它就支持了多種數值型別。

```haskell
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
vectMult :: (Num t) => Vector t -> t -> Vector t
(Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
scalarMult :: (Num t) => Vector t -> Vector t -> t
(Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
```

`vplus` 用來相加兩個向量，即將其所有對應的項相加。`scalarMult` 用來求兩個向量的標量積，`vectMult` 求一個向量和一個標量的積。這些函數可以處理 `Vector Int`，`Vector Integer`，`Vector Float` 等等型別，只要 `Vector a` 裡的這個 `a` 在 `Num` 型別類中就行。同樣，如果你看下這些函數的型別聲明就會發現，它們只能處理相同型別的向量，其中包含的數字型別必須與另一個向量一致。注意，我們並沒有在 `data` 聲明中添加 `Num` 的類約束。反正無論怎麼著都是給函數加約束。

再度重申，型別構造子和值構造子的區分是相當重要的。在聲明數據型別時，等號=左端的那個是型別構造子，右端的(中間可能有|分隔)都是值構造子。拿 `Vector t t t -> Vector t t t -> t` 作函數的型別就會產生一個錯誤，因為在型別聲明中只能寫型別，而 `Vector` 的型別構造子只有個參數，它的值構造子才是有三個。我們就慢慢耍：

```haskell
ghci> Vector 3 5 8 `vplus` Vector 9 2 8
Vector 12 7 16
ghci> Vector 3 5 8 `vplus` Vector 9 2 8 `vplus` Vector 0 2 3
Vector 12 9 19
ghci> Vector 3 9 7 `vectMult` 10
Vector 30 90 70
ghci> Vector 4 9 5 `scalarMult` Vector 9.0 2.0 4.0
74.0
ghci> Vector 2 9 3 `vectMult` (Vector 4 9 5 `scalarMult` Vector 9 2 4)
Vector 148 666 222
```

## Derived instances

![](/files/-LjUmqjj5OOQiKw-zb0Z)

在 \[types-and-type-classes.html#Typeclasses入門 Typeclass 101] 那一節裡面，我們瞭解了 Typeclass 的基礎內容。裡面提到，型別類就是定義了某些行為的介面。例如，Int 型別是 `Eq` 型別類的一個 instance，`Eq` 類就定義了判定相等性的行為。Int 值可以判斷相等性，所以 Int 就是 `Eq` 型別類的成員。它的真正威力體現在作為 `Eq` 介面的函數中，即 `==` 和 `/=`。只要一個型別是 `Eq` 型別類的成員，我們就可以使用 `==` 函數來處理這一型別。這便是為何 `4==4` 和 `"foo"/="bar"` 這樣的表達式都需要作型別檢查。

我們也曾提到，人們很容易把型別類與 Java，Python，C++ 等語言的類混淆。很多人對此都倍感不解，在原先那些語言中，類就像是藍圖，我們可以根據它來創造對象、保存狀態並執行操作。而型別類更像是介面，我們不是靠它構造數據，而是給既有的數據型別描述行為。什麼東西若可以判定相等性，我們就可以讓它成為 `Eq` 型別類的 instance。什麼東西若可以比較大小，那就可以讓它成為 `Ord` 型別類的 instance。

在下一節，我們將看一下如何手工實現型別類中定義函數來構造 instance。現在呢，我們先瞭解下 Haskell 是如何自動生成這幾個型別類的 instance，`Eq`, `Ord`, `Enum`, `Bounded`, `Show`, `Read`。只要我們在構造型別時在後面加個 `deriving`(派生)關鍵字，Haskell 就可以自動地給我們的型別加上這些行為。

看這個數據型別：

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     }
```

這描述了一個人。我們先假定世界上沒有重名重姓又同齡的人存在，好，假如有兩個 record，有沒有可能是描述同一個人呢？當然可能，我麼可以判定姓名年齡的相等性，來判斷它倆是否相等。這一來，讓這個型別成為 `Eq` 的成員就很靠譜了。直接 derive 這個 instance：

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq)
```

在一個型別 derive 為 `Eq` 的 instance 後，就可以直接使用 `==` 或 `/=` 來判斷它們的相等性了。Haskell 會先看下這兩個值的值構造子是否一致(這裡只是單值構造子)，再用 `==` 來檢查其中的所有數據(必須都是 `Eq` 的成員)是否一致。在這裡只有 `String` 和 Int，所以是沒有問題的。測試下我們的 Eqinstance：

```haskell
ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}
ghci> let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}
ghci> mca == adRock
False
ghci> mikeD == adRock
False
ghci> mikeD == mikeD
True
ghci> mikeD == Person {firstName = "Michael", lastName = "Diamond", age = 43}
True
```

自然，`Person` 如今已經成為了 `Eq` 的成員，我們就可以將其應用於所有在型別聲明中用到 `Eq` 類約束的函數了，如 `elem`。

```haskell
ghci> let beastieBoys = [mca, adRock, mikeD]
ghci> mikeD `elem` beastieBoys
True
```

`Show` 和 `Read` 型別類處理可與字元串相互轉換的東西。同 `Eq` 相似，如果一個型別的構造子含有參數，那所有參數的型別必須都得屬於 `Show` 或 `Read` 才能讓該型別成為其 instance。就讓我們的 `Person` 也成為 `Read` 和 `Show` 的一員吧。

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq, Show, Read)
```

然後就可以輸出一個 `Person` 到控制台了。

```haskell
ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> mikeD
Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> "mikeD is: " ++ show mikeD
"mikeD is: Person {firstName = \"Michael\", lastName = \"Diamond\", age = 43}"
```

如果我們還沒讓 `Person` 型別作為 `Show` 的成員就嘗試輸出它，Haskell 就會向我們抱怨，說它不知道該怎麼把它表示成一個字元串。不過現在既然已經 derive 成為了 `Show` 的一個 instance，它就知道了。

`Read` 几乎就是與 `Show` 相對的型別類，`show` 是將一個值轉換成字元串，而 `read` 則是將一個字元串轉成某型別的值。還記得，使用 `read` 函數時我們必須得用型別註釋註明想要的型別，否則 Haskell 就不會知道如何轉換。

```haskell
ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" :: Person
Person {firstName = "Michael", lastName = "Diamond", age = 43}
```

如果我們 `read` 的結果會在後面用到參與計算，Haskell 就可以推導出是一個 Person 的行為，不加註釋也是可以的。

```haskell
ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" == mikeD
True
```

也可以 `read` 帶參數的型別，但必須填滿所有的參數。因此 `read "Just 't'" :: Maybe a` 是不可以的，`read "Just 't'" :: Maybe Char` 才對。

很容易想象 `Ord` 型別類 derive instance 的行為。首先，判斷兩個值構造子是否一致，如果是，再判斷它們的參數，前提是它們的參數都得是 `Ord` 的 instance。`Bool` 型別可以有兩種值，`False` 和 `True`。為了瞭解在比較中程序的行為，我們可以這樣想象：

```haskell
data Bool = False | True deriving (Ord)
```

由於值構造子 `False` 安排在 `True` 的前面，我們可以認為 `True` 比 `False` 大。

```haskell
ghci> True `compare` False
GT
ghci> True > False
True
ghci> True < False
False
```

在 `Maybe a` 數據型別中，值構造子 `Nothing` 在 `Just` 值構造子前面，所以一個 `Nothing` 總要比 `Just something` 的值小。即便這個 `something` 是 `-100000000` 也是如此。

```haskell
ghci> Nothing < Just 100
True
ghci> Nothing > Just (-49999)
False
ghci> Just 3 `compare` Just 2
GT
ghci> Just 100 > Just 50
True
```

不過類似 `Just (*3) > Just (*2)` 之類的程式碼是不可以的。因為 `(*3)` 和 `(*2)` 都是函數，而函數不是 `Ord` 類的成員。

作枚舉，使用數字型別就能輕易做到。不過使用 `Enum` 和 `Bounded` 型別類會更好，看下這個型別：

```haskell
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
```

所有的值構造子都是 `nullary` 的(也就是沒有參數)，每個東西都有前置子和後繼子，我們可以讓它成為 `Enum` 型別類的成員。同樣，每個東西都有可能的最小值和最大值，我們也可以讓它成為 `Bounded` 型別類的成員。在這裡，我們就同時將它搞成其它可 derive型別類的 instance。再看看我們能拿它做啥：

```haskell
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
           deriving (Eq, Ord, Show, Read, Bounded, Enum)
```

由於它是 `Show` 和 `Read` 型別類的成員，我們可以將這個型別的值與字元串相互轉換。

```haskell
ghci> Wednesday
Wednesday
ghci> show Wednesday
"Wednesday"
ghci> read "Saturday" :: Day
Saturday
```

由於它是 `Eq` 與 `Ord` 的成員，因此我們可以拿 `Day` 作比較。

```haskell
ghci> Saturday == Sunday
False
ghci> Saturday == Saturday
True
ghci> Saturday > Friday
True
ghci> Monday `compare` Wednesday
LT
```

它也是 `Bounded` 的成員，因此有最早和最晚的一天。

```haskell
ghci> minBound :: Day
Monday
ghci> maxBound :: Day
Sunday
```

它也是 `Enum` 的 instance，可以得到前一天和後一天，並且可以對此使用 List 的區間。

```haskell
ghci> succ Monday
Tuesday
ghci> pred Saturday
Friday
ghci> [Thursday .. Sunday]
[Thursday,Friday,Saturday,Sunday]
ghci> [minBound .. maxBound] :: [Day]
[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]
```

那是相當的棒。

## Type synonyms

在前面我們提到在寫型別名的時候，`[Char]` 和 `String` 等價，可以互換。這就是由型別別名實現的。型別別名實際上什麼也沒做，只是給型別提供了不同的名字，讓我們的程式碼更容易理解。這就是 `[Char]` 的別名 `String` 的由來。

```haskell
type String = [Char]
```

我們已經介紹過了 `type` 關鍵字，這個關鍵字有一定誤導性，它並不是用來創造新類（這是 `data` 關鍵字做的事情），而是給一個既有型別提供一個別名。

如果我們隨便搞個函數 `toUpperString` 或其他什麼名字，將一個字元串變成大寫，可以用這樣的型別聲明 `toUpperString :: [Char] -> [Char]`， 也可以這樣 `toUpperString :: String -> String`，二者在本質上是完全相同的。後者要更易讀些。

在前面 `Data.Map` 那部分，我們用了一個關聯 `List` 來表示 `phoneBook`，之後才改成的 Map。我們已經發現了，一個關聯 List 就是一組鍵值對組成的 List。再看下我們 `phoneBook` 的樣子：

```haskell
phoneBook :: [(String,String)]
phoneBook =
    [("betty","555-2938")
    ,("bonnie","452-2928")
    ,("patsy","493-2928")
    ,("lucille","205-2928")
    ,("wendy","939-8282")
    ,("penny","853-2492")
    ]
```

可以看出，`phoneBook` 的型別就是 `[(String,String)]`，這表示一個關聯 List 僅是 String 到 String 的映射關係。我們就弄個型別別名，好讓它型別聲明中能夠表達更多資訊。

```haskell
type PhoneBook = [(String,String)]
```

現在我們 `phoneBook` 的型別聲明就可以是 `phoneBook :: PhoneBook` 了。再給字元串加上別名：

```haskell
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
```

Haskell 程序員給 String 加別名是為了讓函數中字元串的表達方式及用途更加明確。

好的，我們實現了一個函數，它可以取一名字和號碼檢查它是否存在於電話本。現在可以給它加一個相當好看明了的型別聲明：

```haskell
inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool
inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook
```

![](/files/-LjUmqjtNN2bPgMS4_Fa)

如果不用型別別名，我們函數的型別聲明就只能是 `String -> String -> [(String ,String)] -> Bool` 了。在這裡使用型別別名是為了讓型別聲明更加易讀，但你也不必拘泥于它。引入型別別名的動機既非單純表示我們函數中的既有型別，也不是為了替換掉那些重複率高的長名字型別(如 `[(String,String)]`)，而是為了讓型別對事物的描述更加明確。

型別別名也是可以有參數的，如果你想搞個型別來表示關聯 List，但依然要它保持通用，好讓它可以使用任意型別作 `key` 和 `value`，我們可以這樣：

```haskell
type AssocList k v = [(k,v)]
```

好的，現在一個從關聯 List 中按鍵索值的函數型別可以定義為 `(Eq k) => k -> AssocList k v -> Maybe v. AssocList i`。`AssocList` 是個取兩個型別做參數生成一個具體型別的型別構造子，如 `Assoc Int String` 等等。

```
*Fronzie 說*：Hey！當我提到具體型別，那我就是說它是完全呼叫的，就像 ``Map Int String``。要不就是多態函數中的 ``[a]`` 或 ``(Ord a) => Maybe a`` 之類。有時我和孩子們會說 "Maybe 型別"，但我們的意思並不是按字面來，傻瓜都知道 ``Maybe`` 是型別構造子嘛。只要用一個明確的型別呼叫 ``Maybe``，如 ``Maybe String`` 可得一個具體型別。你知道，只有具體型別才可以儲存值。
```

我們可以用不全呼叫來得到新的函數，同樣也可以使用不全呼叫得到新的型別構造子。同函數一樣，用不全的型別參數呼叫型別構造子就可以得到一個不全呼叫的型別構造子，如果我們要一個表示從整數到某東西間映射關係的型別，我們可以這樣：

```haskell
type IntMap v = Map Int v
```

也可以這樣：

```haskell
type IntMap = Map Int
```

無論怎樣，`IntMap` 的型別構造子都是取一個參數，而它就是這整數指向的型別。

Oh yeah，如果要你去實現它，很可能會用個 `qualified import` 來導入 `Data.Map`。這時，型別構造子前面必須得加上模組名。所以應該寫個 `type IntMap = Map.Map Int`

你得保證真正弄明白了型別構造子和值構造子的區別。我們有了個叫 `IntMap` 或者 `AssocList` 的別名並不意味着我們可以執行類似 `AssocList [(1,2),(4,5),(7,9)]` 的程式碼，而是可以用不同的名字來表示原先的 List，就像 `[(1,2),(4,5),(7,9)] :: AssocList Int Int` 讓它裡面的型別都是 Int。而像處理普通的 Tuple 構成的那種 List 處理它也是可以的。型別別名(型別依然不變)，只可以在 Haskell 的型別部分中使用，像定義新型別或型別聲明或型別註釋中跟在::後面的部分。

另一個很酷的二參型別就是 `Either a b` 了，它大約是這樣定義的：

```haskell
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
```

它有兩個值構造子。如果用了 `Left`，那它內容的型別就是 `a`；用了 `Right`，那它內容的型別就是 `b`。我們可以用它來將可能是兩種型別的值封裝起來，從裡面取值時就同時提供 `Left` 和 `Right` 的模式匹配。

```haskell
ghci> Right 20
Right 20
ghci> Left "w00t"
Left "w00t"
ghci> :t Right 'a'
Right 'a' :: Either a Char
ghci> :t Left True
Left True :: Either Bool b
```

到現在為止，`Maybe` 是最常見的表示可能失敗的計算的型別了。但有時 `Maybe` 也並不是十分的好用，因為 `Nothing` 中包含的信息還是太少。要是我們不關心函數失敗的原因，它還是不錯的。就像 `Data.Map` 的 `lookup` 只有在搜尋的項不在 Map 時才會失敗，對此我們一清二楚。但我們若想知道函數失敗的原因，那還得使用 `Either a b`，用 `a` 來表示可能的錯誤的型別，用 `b` 來表示一個成功運算的型別。從現在開始，錯誤一律用 `Left` 值構造子，而結果一律用 `Right`。

一個例子：有個學校提供了不少壁櫥，好給學生們地方放他們的 Gun'N'Rose 海報。每個壁櫥都有個密碼，哪個學生想用個壁櫥，就告訴管理員壁櫥的號碼，管理員就會告訴他壁櫥的密碼。但如果這個壁櫥已經讓別人用了，管理員就不能告訴他密碼了，得換一個壁櫥。我們就用 `Data.Map` 的一個 Map 來表示這些壁櫥，把一個號碼映射到一個表示壁櫥占用情況及密碼的 Tuple 裡。

```haskell
import qualified Data.Map as Map

data LockerState = Taken | Free deriving (Show, Eq)

type Code = String

type LockerMap = Map.Map Int (LockerState, Code)
```

很簡單，我們引入了一個新的型別來表示壁櫥的占用情況。併為壁櫥密碼及按號碼找壁櫥的 Map 分別設置了一個別名。好，現在我們實現這個按號碼找壁櫥的函數，就用 `Either String Code` 型別表示我們的結果，因為 `lookup` 可能會以兩種原因失敗。櫥子已經讓別人用了或者壓根就沒有這個櫥子。如果 `lookup` 失敗，就用字元串表明失敗的原因。

```haskell
lockerLookup :: Int -> LockerMap -> Either String Code
lockerLookup lockerNumber map =
    case Map.lookup lockerNumber map of
        Nothing -> Left $ "Locker number " ++ show lockerNumber ++ " doesn't exist!"
        Just (state, code) -> if state /= Taken
                                then Right code
                                else Left $ "Locker " ++ show lockerNumber ++ " is already taken!"
```

我們在這裡個 Map 中執行一次普通的 `lookup`，如果得到一個 `Nothing`，就返回一個 `Left String` 的值，告訴他壓根就沒這個號碼的櫥子。如果找到了，就再檢查下，看這櫥子是不是已經讓別人用了，如果是，就返回個 `Left String` 說它已經讓別人用了。否則就返回個 `Right Code` 的值，通過它來告訴學生壁櫥的密碼。它實際上就是個 `Right String`，我們引入了個型別別名讓它這型別聲明更好看。

如下是個 Map 的例子：

```haskell
lockers :: LockerMap
lockers = Map.fromList
    [(100,(Taken,"ZD39I"))
    ,(101,(Free,"JAH3I"))
    ,(103,(Free,"IQSA9"))
    ,(105,(Free,"QOTSA"))
    ,(109,(Taken,"893JJ"))
    ,(110,(Taken,"99292"))
    ]
```

現在從裡面 `lookup` 某個櫥子號..

```haskell
ghci> lockerLookup 101 lockers
Right "JAH3I"
ghci> lockerLookup 100 lockers
Left "Locker 100 is already taken!"
ghci> lockerLookup 102 lockers
Left "Locker number 102 doesn't exist!"
ghci> lockerLookup 110 lockers
Left "Locker 110 is already taken!"
ghci> lockerLookup 105 lockers
Right "QOTSA"
```

我們完全可以用 `Maybe a` 來表示它的結果，但這樣一來我們就對得不到密碼的原因不得而知了。而在這裡，我們的新型別可以告訴我們失敗的原因。

## Recursive data structures (遞迴地定義資料結構)

如我們先前看到的，一個 algebraic data type 的構造子可以有好幾個 field，其中每個 field 都必須有具體的型態。有了那個概念，我們能定義一個型態，其中他的構造子的 field 的型態是他自己。這樣我們可以遞迴地定義下去，某個型態的值便可能包含同樣型態的值，進一步下去他還可以再包含同樣型態的值。

考慮一下 List: `[5]`。他其實是 `5:[]` 的語法糖。在 `:` 的左邊是一個普通值，而在右邊是一串 List。只是在這個案例中是空的 List。再考慮 `[4,5]`。他可以看作 `4:(5:[])`。看看第一個 `:`，我們看到他也有一個元素在左邊，一串 List `5:[]` 在右邊。同樣的道理 `3:(4:(5:6:[]))` 也是這樣。

我們可以說一個 List 的定義是要碼是空的 List 或是一個元素，後面用 `:` 接了另一串 List。

我們用 algebraic data type 來實作我們自己的 List！

```haskell
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
```

這讀起來好像我們前一段提及的定義。他要碼是空的 List，或是一個元素跟一串 List 的結合。如果你被搞混了，看看用 record syntax 定義的可能比較清楚。

```haskell
data List a = Empty | Cons { listHead :: a, listTail :: List a} deriving (Show, Read, Eq, Ord)
```

你可能也對這邊的 `Cons` 構造子不太清楚。`cons` 其實就是指 `:`。對 List 而言，`:` 其實是一個構造子，他接受一個值跟另一串 List 來構造一個 List。現在我們可以使用我們新定義的 List 型態。換句話說，他有兩個 field，其中一個 field 具有型態 `a`，另一個有型態 `[a]`。

```haskell
ghci> Empty
Empty
ghci> 5 `Cons` Empty
Cons 5 Empty
ghci> 4 `Cons` (5 `Cons` Empty)
Cons 4 (Cons 5 Empty)
ghci> 3 `Cons` (4 `Cons` (5 `Cons` Empty))
Cons 3 (Cons 4 (Cons 5 Empty))
```

我們用中綴的方式呼叫 `Cons` 構造子，這樣你可以很清楚地看到他就是 `:`。`Empty` 代表 `[]`，而 ``4 `Cons` (5 `Cons` Empty)`` 就是 `4:(5:[])`。

我們可以只用特殊字元來定義函數，這樣他們就會自動具有中綴的性質。我們也能同樣的手法套用在構造子上，畢竟他們不過是回傳型態的函數而已。

```haskell
infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
```

首先我們留意新的語法結構：fixity 宣告。當我們定義函數成 operator，我們能同時指定 fixity (但並不是必須的)。fixity 指定了他應該是 left-associative 或是 right-associative，還有他的優先順序。例如說，`*` 的 fixity 是 `infixl 7 *`，而 `+` 的 fixity 是 `infixl 6`。代表他們都是 left-associative。`(4 * 3 * 2)` 等於 `((4 * 3) * 2)`。但 `*` 擁有比 `+` 更高的優先順序。所以 `5 * 4 + 3` 會是 `(5 * 4) + 3`。

這樣我們就可以寫成 `a :-: (List a)` 而不是 `Cons a (List a)`：

```haskell
ghci> 3 :-: 4 :-: 5 :-: Empty
(:-:) 3 ((:-:) 4 ((:-:) 5 Empty))
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> 100 :-: a
(:-:) 100 ((:-:) 3 ((:-:) 4 ((:-:) 5 Empty)))
```

Haskell 在宣告 `deriving Show` 的時候，他會仍視構造子為前綴函數，因此必須要用括號括起來。

我們在來寫個函數來把兩個 List 連起來。一般 `++` 在操作普通 List 的時候是這樣的：

```haskell
infixr 5  ++
(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
```

我們把他偷過來用在我們的 List 上，把函數命名成 `.++`：

```haskell
infixr 5  .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
(x :-: xs) .++ ys = x :-: (xs .++ ys)
```

來看看他如何運作：

```haskell
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> let b = 6 :-: 7 :-: Empty
ghci> a .++ b
(:-:) 3 ((:-:) 4 ((:-:) 5 ((:-:) 6 ((:-:) 7 Empty))))
```

如果我們想要的話，我們可以定義其他操作我們list的函數。

注意到我們是如何利用 `(x :-: xs)` 做模式匹配的。他運作的原理實際上就是利用到構造子。我們可以利用 `:-:` 做模式匹配原因就是他是構造子，同樣的 `:` 也是構造子所以可以用他做匹配。`[]` 也是同樣道理。由於模式匹配是用構造子來作的，所以我們才能對像 `8`, `'a'` 之類的做模式匹配。他們是數值與字元的構造子。

接下來我們要實作二元搜尋樹 (binary search tree)。如果你對二元搜尋樹不太清楚，我們來快速地解釋一遍。他的結構是每個節點指向兩個其他節點，一個在左邊一個在右邊。在左邊節點的元素會比這個節點的元素要小。在右邊的話則比較大。每個節點最多可以有兩棵子樹。而我們知道譬如說一棵包含 5 的節點的左子樹，裡面所有的元素都會小於 5。而節點的右子樹裡面的元素都會大於 5。如果我們想找找看 8是不是在我們的樹裡面，我們就從 5 那個節點找起，由於 8 比 5 要大，很自然地就會往右搜尋。接著我們走到 7，又由於 8 比 7 要大，所以我們再往右走。我們在三步就找到了我們要的元素。如果這不是棵樹而是 List 的話，那就會需要花到七步才能找到 8。

`Data.Set` 跟 `Data.Map` 中的 `set` 和 Map 都是用樹來實現的，只是他們是用平衡二元搜尋樹而不是隨意的二元搜尋樹。不過這邊我們就只先寫一棵普通的二元搜尋樹就好了。

這邊我們來定義一棵樹的結構：他不是一棵空的樹就是帶有值並含有兩棵子樹。聽起來非常符合 algebraic data type 的結構！

```haskell
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
```

我們不太想手動來建棵二元搜尋樹，所以我們要來寫一個函數，他接受一棵樹還有一個元素，把這個元素安插到這棵二元搜尋樹中。當拿這個元素跟樹的節點比較結果比較小的話，我們就往左走，如果比較大，就往右走。重複這個動作直到我們走到一棵空的樹。一旦碰到空的樹的話，我們就把元素插入節點。

在 C 語言中，我們是用修改指標的方式來達成這件事。但在 Haskell 中，我們沒辦法修改我們的樹。所以我們在決定要往左或往右走的時候就做一棵新的子樹，走到最後要安插節點的時候也是做一棵新的樹。因此我們插入函數的型態會是 `a -> Tree a -> Tree a`。他接受一個元素跟一棵樹，並回傳一棵包含了新元素的新的樹。這看起來很沒效率的樣子，但別擔心，惰性的特性可以讓我們不用擔心這個。

來看下列兩個函數。第一個做了一個單節點的樹，而第二個插入一個元素到一棵樹中。

```haskell
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
      | x == a = Node x left right
      | x < a  = Node a (treeInsert x left) right
      | x > a  = Node a left (treeInsert x right)
```

`singleton` 函數只是一個做一個含有兩棵空子樹的節點的函數的別名。在插入的操作中，我們先為終端條件定義了一個模式匹配。如果我們走到了一棵空的子樹，這表示我們到達了我們想要的地方，我們便建造一棵空的單元素的樹來放在那個位置。如果我們還沒走到一棵空的樹來插入我們的元素。那就必須要做一些檢查來往下走。如果我們要安插的元素跟 root 所含有的元素相等，那就直接回傳這棵樹。如果安插的元素比較小，就回傳一棵新的樹。這棵樹的 root 跟原來的相同，右子樹也相同，只差在我們要安插新的元素到左子樹中。如果安插的元素反而比較大，那整個過程就相反。

接下來，我們要寫一個函數來檢查某個元素是否已經在這棵樹中。首先我們定義終端條件。如果我們已經走到一棵空的樹，那這個元素一定不在這棵樹中。這跟我們搜尋 List 的情形是一致的。如果我們要在空的 List 中搜尋某一元素，那就代表他不在這個 List 裡面。假設我們現在搜尋一棵非空的樹，而且 root 中的元素剛好就是我們要的，那就找到了。那如果不是呢？我們就要利用在 root 節點左邊的元素都比 root 小的這個性質。如果我們的元素比 root 小，那就往左子樹中找。如果比較大，那就往右子樹中找。

```haskell
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
    | x == a = True
    | x < a  = treeElem x left
    | x > a  = treeElem x right
```

我們要作的就是把之前段落所描述的事轉換成程式碼。首先我們不想手動一個個來創造一棵樹。我們想用一個 `fold` 來從一個 List 創造一棵樹。要知道走遍一個 List 並回傳某種值的操作都可以用 `fold` 來實現。我們先從一棵空的樹開始，然後從右邊走過 List的 每一個元素，一個一個丟到樹裡面。

```haskell
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))
```

在 `foldr` 中，`treeInsert` 是做 folding 操作的函數，而 `EmptyTree` 是起始的 accumulator，`nums` 則是要被走遍的 List。

當我們想把我們的樹印出來的時候，印出來的形式會不太容易讀。但如果我們能有結構地印出來呢？我們知道 root 是 5，他有兩棵子樹，其中一個的 root 是 3 另一個則是 7。

```haskell
ghci> 8 `treeElem` numsTree
True
ghci> 100 `treeElem` numsTree
False
ghci> 1 `treeElem` numsTree
True
ghci> 10 `treeElem` numsTree
False
```

檢查元素是否屬於某棵樹的函數現在能正常運作了！

你可以看到 algebraic data structures 是非常有力的概念。我們可以使用這個結構來構造出布林值，週一到週五的概念，甚至還有二元樹。

## Typeclasses 的第二堂課

到目前為止我們學到了一些 Haskell 中的標準 typeclass，也學到了某些已經定義為他們 instance 的型別。我們知道如何讓我們自己定義的型別自動被 Haskell 所推導成標準 typeclass 的 instance。在這個章節中，我們會學到如何構造我們自己的 typeclass，並且如何構造這些 typeclass 的 type instance。

來快速複習一下什麼是 typeclass: typeclass 就像是 interface。一個 typeclass 定義了一些行為(像是比較相不相等，比較大小順序，能否窮舉)而我們會把希望滿足這些性質的型別定義成這些 typeclass 的 instance。typeclass 的行為是由定義的函數來描述。並寫出對應的實作。當我們把一個型別定義成某個 typeclass 的 instance，就表示我們可以對那個型別使用 typeclass 中定義的函數。

Typeclass 跟 Java 或 Python 中的 class 一點關係也沒有。這個概念讓很多人混淆，所以我希望你先忘掉所有在命令式語言中學到有關 class 的所有東西。

例如說，`Eq` 這個 typeclass 是描述可以比較相等的事物。他定義了 `==` 跟 `/=` 兩個函數。如果我們有一個型別 `Car`，而且對他們做相等比較是有意義的，那把 `Car` 作成是 `Eq` 的一個 instance 是非常合理的。

這邊來看看在 `Prelude` 之中 `Eq` 是怎麼被定義的。

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)
```

我們在這邊看到了一些奇怪的語法跟關鍵字。別擔心，你一下子就會瞭解他們的。首先，我們看到 `class Eq a where`，那代表我們定義了一個新的 typeclass 叫做 `Eq`。`a` 是一個型別變數，他代表 `a` 是任何我們在定義 instance 時的型別。他不一定要叫做 `a`。他也不一定非要一個字母不可，只要他是小寫就好。然後我們又定義了幾個函數。我們並不一定要實作函數的本體，不過必須要寫出函數的型別宣告。

如果我們寫成 `class Eq equatable where` 還有 `(==) :: equatable -> equatable -> Bool` 這樣的形式，對一些人可能比較容易理解。

總之我們實作了 `Eq` 中需要定義的函數本體，只是我們定義他的方式是用交互遞迴的形式。我們描述兩個 `Eq` 的 instance 要相等，那他們就不能不一樣，而他們如果不一樣，那他們就是不相等。我們其實不必這樣寫，但很快你會看到這其實是有用的。

如果我們說 `class Eq a where` 然後定義 `(==) :: a -> a -> Bool`，那我們之後檢查函數的型別時會發現他的型別是 `(Eq a) => a -> a -> Bool`。

當我們有了 class 以後，可以用來做些什麼呢？說實話，不多。不過一旦我們為它寫一些 instance，就會有些好功能。來看看下面這個型別：

```haskell
data TrafficLight = Red | Yellow | Green
```

這裡定義了紅綠燈的狀態。請注意這個型別並不是任何 class 的 instance，雖然可以透過 derive 讓它成為 `Eq` 或 `Show` 的 instance，但我們打算手工打造。下面展示了如何讓一個型別成為 `Eq` 的 instance：

```haskell
instance Eq TrafficLight where
    Red == Red = True
    Green == Green = True
    Yellow == Yellow = True
    _ == _ = False
```

我們使用了 `instance` 這個關鍵字。class 是用來定義新的 typeclass，而 instance 是用來說明我們要定義某個 typeclass 的 instance。當我們要定義 `Eq`，我們會寫 `class Eq a where`，其中 `a` 代表任何型態。我們可以從 instance 的寫法：`instance Eq TrafficLight where` 看出來。我們會把 `a` 換成實際的型別。

由於 `==` 是用 `/=` 來定義的，同樣的 `/=` 也是用 `==` 來定義。所以我們只需要在 instance 定義中複寫其中一個就好了。我們這樣叫做定義了一個 minimal complete definition。這是說能讓型別符合 class 行為所最小需要實作的函數數量。而 `Eq` 的 minimal complete definition 需要 `==` 或 `/=` 其中一個。而如果 `Eq` 是這樣定義的：

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
```

當我們定義 instance 的時候必須要兩個函數都實作，因為 Haskell 並不知道這兩個函數是怎麼關聯在一起的。所以 minimal complete definition 在這邊是 `==` 跟 `/=`。

你可以看到我們是用模式匹配來實作 `==`。由於不相等的情況比較多，所以我們只寫出相等的，最後再用一個 `case` 接住說你不在前面相等的 `case` 的話，那就是不相等。

我們再來寫 `Show` 的 instance。要滿足 `Show` 的 minimal complete definition，我們必須實作 `show` 函數，他接受一個值並把他轉成字串。

```haskell
instance Show TrafficLight where
    show Red = "Red light"
    show Yellow = "Yellow light"
    show Green = "Green light"
```

再一次地，我們用模式匹配來完成我們的任務。我們來看看他是如何運作的。

```haskell
ghci> Red == Red
True
ghci> Red == Yellow
False
ghci> Red `elem` [Red, Yellow, Green]
True
ghci> [Red, Yellow, Green]
[Red light,Yellow light,Green light]
```

如果我們用 `derive` 來自動產生 `Eq` 的話，效果是一樣的。不過用 `derive` 來產生 `show` 的話，他會把值構造子轉換成字串。但我們這邊要的不太一樣，我們希望印出像 `"Red light"` 這樣的字串，所以我們就必須手動來寫出 instance。

你也可以把 typeclass 定義成其他 typeclass 的 subclass。像是 `Num` 的 class 宣告就有點冗長，但我們先看個雛型。

```haskell
class (Eq a) => Num a where
   ...
```

正如我們先前提到的，我們可以在很多地方加上 class constraints。這不過就是在 `class Num a where` 中的 `a` 上，加上他必須要是 `Eq` 的 instance 的限制。這基本上就是在說我們在定義一個型別為 `Num` 之前，必須先為他定義 `Eq` 的 instance。在某個型別可以被視作 `Number` 之前，必須先能被比較相不相等其實是蠻合理的。這就是 subclass 在做的事：幫 class declaration 加上限制。也就是說當我們定義 typeclass 中的函數本體時，我們可以預設 `a` 是屬於 `Eq`，因此能使用 `==`。

但像是 `Maybe` 或是 List 是如何被定義成 typeclass 的 instance 呢？`Maybe` 的特別之處在於他跟 `TrafficLight` 不一樣，他不是一個具體的型別。他是一個型別構造子，接受一個型別參數（像是 `Char` 之類的）而構造出一個具體的型別（像是 `Maybe Char` ）。讓我們再回顧一下 `Eq` 這個 typeclass：

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)
```

從型別宣告來看，可以看到 `a` 必須是一個具體型別，因為所有在函數中的型別都必須是具體型別。(你沒辦法寫一個函數，他的型別是 `a -> Maybe`，但你可以寫一個函數，他的型別是 `a -> Maybe a`，或是 `Maybe Int -> Maybe String`) 這就是為什麼我們不能寫成像這樣：

```haskell
instance Eq Maybe where
    ...
```

```haskell
instance Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False
```

這就好像在說我們要把 `Maybe something` 這種東西全部都做成 `Eq` 的 instance。我們的確可以寫成 `(Maybe something)`，但我們通常是只用一個字母，這樣比較像是 Haskell 的風格。`(Maybe m)` 這邊則取代了 `a` 在 `class Eq a where` 的位置。儘管 `Maybe` 不是一個具體的型別。`Maybe m` 卻是。指定一個型別參數（在這邊是小寫的 `m`），我們說我們想要所有像是 `Maybe m` 的都成為 `Eq` 的 instance。

不過這仍然有一個問題。你能看出來嗎？ 我們用 `==` 來比較 `Maybe` 包含的東西，但我們並沒有任何保證說 `Maybe` 裝的東西可以是 `Eq`。這就是為什麼我們需要修改我們的 instance 定義：

```haskell
instance (Eq m) => Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False
```

這邊我們必須要加上限制。在這個 instance 的宣告中，我們希望所有 `Maybe m` 形式的型別都屬於 `Eq`，但只有當 `m` 也屬於 `Eq` 的時候。這也是 Haskell 在 derive 的時候做的事。

在大部份情形下，在 typeclass 宣告中的 class constraints 都是要讓一個 typeclass 成為另一個 typeclass 的 subclass。而在 instance 宣告中的 class constraint 則是要表達型別的要求限制。舉裡來說，我們要求 `Maybe` 的內容物也要屬於 `Eq`。

當定義 instance 的時候，如果你需要提供具體型別（像是在 `a -> a -> Bool` 中的 `a`），那你必須要加上括號跟型別參數來構造一個具體型別。

要知道你在定義 instance 的時候，型別參數會被取代。`class Eq a where` 中的 `a` 會被取代成真實的型別。所以試著想像把型別放進型別宣告中。`(==) :: Maybe -> Maybe -> Bool` 並非合法。但 `(==) :: (Eq m) => Maybe m -> Maybe m -> Bool` 則是。這是不論我們要定義什麼，通用的型別宣告都是 `(==) :: (Eq a) => a -> a -> Bool`

還有一件事要確認。如果你想看看一個 typeclass 有定義哪些 instance。可以在 ghci 中輸入 `:info YourTypeClass`。所以輸入 `:info Num` 會告訴你這個 typeclass 定義了哪些函數，還有哪些型別屬於這個 typeclass。`:info` 也可以查詢型別跟型別構造子的資訊。如果你輸入 `:info Maybe`。他會顯示 `Maybe` 所屬的所有 typeclass。`:info` 也能告訴你函數的型別宣告。

## yes-no typeclass

在 Javascript 或是其他弱型別的程式語言，你能在 if expression 中擺上任何東西。舉例來說，你可以做像下列的事： `if (0) alert("YEAH!") else alert("NO!")`, `if ("") alert ("YEAH!") else alert("NO!")`, `if (false) alert("YEAH") else alert("NO!)` 等等， 而上述所有的片段執行後都會跳出 `NO!`。如果你寫 `if ("WHAT") alert ("YEAH") else alert("NO!")`，他會跳出 `YEAH!`，因為 Javascript 認為非空字串會是 true。

儘管使用 `Bool` 來表達布林的語意是比較好的作法。為了有趣起見，我們來試試看模仿 Javascript 的行為。我們先從 typeclass 宣告開始看：

```haskell
class YesNo a where
    yesno :: a -> Bool
```

`YesNo` typeclass 定義了一個函數。這個函數接受一個可以判斷為真否的型別的值。而從我們寫 `a` 的位置，可以看出來 `a` 必須是一個具體型別。

接下來我們來定義一些 instance。對於數字，我們會假設任何非零的數字都會被當作 `true`，而 0 則當作 `false`。

```haskell
instance YesNo Int where
    yesno 0 = False
    yesno _ = True
```

空的 List (包含字串)代表 `false`，而非空的 List 則代表 `true`。

```haskell
instance YesNo [a] where
    yesno [] = False
    yesno _ = True
```

留意到我們加了一個型別參數 `a` 來讓整個 List 是一個具體型別，不過我們並沒有對包涵在 List 中的元素的型別做任何額外假設。我們還剩下 `Bool` 可以被作為真假值，要定義他們也很容易：

```haskell
instance YesNo Bool where
    yesno = id
```

你說 `id` 是什麼？他不過是標準函式庫中的一個函數，他接受一個參數並回傳相同的東西。

我們也讓 `Maybe a` 成為 `YesNo` 的 instance。

```haskell
instance YesNo (Maybe a) where
    yesno (Just _) = True
    yesno Nothing = False
```

由於我們不必對 `Maybe` 的內容做任何假設，因此並不需要 class constraint。我們只要定義遇到 `Just` 包裝過的值就代表 true，而 `Nothing` 則代表 false。這裡還是得寫出 `(Maybe a)` 而不是只有 `Maybe`，畢竟 `Maybe -> Bool` 的函式並不存在（因為 `Maybe` 並不是具體型別），而 `Maybe a -> Bool` 看起來就合理多了。現在有了這個定義，`Maybe something` 型式的型別都屬於 `YesNo` 了，不論 `something` 是什麼。

之前我們定義了 `Tree a`，那代表一個二元搜尋樹。我們可以說一棵空的樹是 `false`，而非空的樹則是 `true`。

```haskell
instance YesNo (Tree a) where
    yesno EmptyTree = False
    yesno _ = True
```

而一個紅綠燈可以代表 yes or no 嗎？當然可以。如果他是紅燈，那你就會停下來，如果他是綠燈，那你就能走。但如果是黃燈呢？只能說我通常會闖黃燈。

```haskell
instance YesNo TrafficLight where
    yesno Red = False
    yesno _ = True
```

現在我們定義了許多 instance，來試著跑跑看！

```haskell
ghci> yesno $ length []
False
ghci> yesno "haha"
True
ghci> yesno ""
False
ghci> yesno $ Just 0
True
ghci> yesno True
True
ghci> yesno EmptyTree
False
ghci> yesno []
False
ghci> yesno [0,0,0]
True
ghci> :t yesno
yesno :: (YesNo a) => a -> Bool
```

很好，統統是我們預期的結果。我們來寫一個函數來模仿 if statement 的行為，但他是運作在 `YesNo` 的型別上。

```haskell
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal yesResult noResult =
    if yesno yesnoVal then yesResult else noResult
```

很直覺吧！他接受一個 yes or no 的值還有兩個部份，如果值是代表 "yes"，那第一個部份就會被執行，而如果值是 "no"，那第二個部份就會執行。

```haskell
ghci> yesnoIf [] "YEAH!" "NO!"
"NO!"
ghci> yesnoIf [2,3,4] "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf True "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf (Just 500) "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf Nothing "YEAH!" "NO!"
"NO!"
```

## Functor typeclass

到目前為止我們看過了許多在標準函式庫中的 typeclass。我們操作過 `Ord`，代表可以被排序的東西。我們也操作過 `Eq`，代表可以被比較相等性的事物。也看過 `Show`，代表可以被印成字串來表示的東西。至於 `Read` 則是我們可以把字串轉換成型別的動作。不過現在我們要來看一下 `Functor` 這個 typeclass，基本上就代表可以被 map over 的事物。聽到這個詞你可能會聯想到 List，因為 map over list 在 Haskell 中是很常見的操作。你沒想錯，List 的確是屬於 `Functor` 這個 typeclass。

來看看他的實作會是瞭解 `Functor` 的最佳方式：

```haskell
class Functor f where
    fmap :: (a -> b) -> f a -> f b
```

我們看到他定義了一個函數 `fmap`，而且並沒有提供一個預設的實作。`fmap` 的型別蠻有趣的。到目前為止的我們看過的 typeclass 中的型別變數都是具體型別。就像是 `(==) :: (Eq a) => a -> a -> Bool` 中的 `a` 一樣。但現在碰到的 `f` 並不是一個具體型別（一個像是 `Int`, `Bool` 或 `Maybe String`的型別），而是接受一個型別參數的型別構造子。如果要快速回顧的話可以看一下 `Maybe Int` 是一個具體型別，而 `Maybe` 是一個型別構造子，可接受一個型別作為參數。總之，我們知道 `fmap` 接受一個函數，這個函數從一個型別映射到另一個型別，還接受一個 functor 裝有原始的型別，然後會回傳一個 functor 裝有映射後的型別。

如果聽不太懂也沒關係。當我們看幾個範例之後會比較好懂。不過這邊 `fmap` 的型別宣告讓我們想起類似的東西，就是 `map :: (a -> b) -> [a] -> [b]`。

他接受一個函數，這函數把一個型別的東西映射成另一個。還有一串裝有某個型別的 List 變成裝有另一個型別的 List。到這邊聽起來實在太像 functor 了。實際上，`map` 就是針對 List 的 `fmap`。來看看 List 是如何被定義成 `Functor` 的 instance 的。

```haskell
instance Functor [] where
    fmap = map
```

注意到我們不是寫成 `instance Functor [a] where`，因為從 `fmap :: (a -> b) -> f a -> f b` 可以知道 `f` 是一個型別構造子，他接受一個型別。而 `[a]` 則已經是一個具體型別（一個擁有某個型別的 List），其中 `[]` 是一個型別構造子，能接受某個型別而構造出像 `[Int]`、`[String]` 甚至是 `[[String]]` 的具體型別。

對於 List，`fmap` 只不過是 `map`，對 List 操作的時候他們都是一樣的。

```haskell
map :: (a -> b) -> [a] -> [b]
ghci> fmap (*2) [1..3]
[2,4,6]
ghci> map (*2) [1..3]
[2,4,6]
```

至於當我們對空的 List 操作 `map` 或 `fmap` 呢？我們會得到一個空的 List。他把一個型別為 `[a]` 的空的 List 轉成型別為 `[b]` 的空的 List。

可以當作盒子的型別可能就是一個 functor。你可以把 List 想做是一個擁有無限小隔間的盒子。他們可能全部都是空的，已也可能有一部份是滿的其他是空的。所以作為一個盒子會具有什麼性質呢？例如說 `Maybe a`。他表現得像盒子在於他可能什麼東西都沒有，就是 `Nothing`，或是可以裝有一個東西，像是 `"HAHA"`，在這邊就是 `Just "HAHA"`。可以看到 `Maybe` 作為一個 functor 的定義：

```haskell
instance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing = Nothing
```

注意到我們是寫 `instance Functor Maybe where` 而不是 `instance Functor (Maybe m) where`，就像我們在寫 `YesNo` 時的 `Maybe` 一樣。`Functor` 要的是一個接受一個型別參數的型別構造子而不是一個具體型別。如果你把 `f` 代換成 `Maybe`。`fmap` 就會像 `(a -> b) -> Maybe a -> Maybe b`。但如果你把 `f` 代換成 `(Maybe m)`，那他就會像 `(a -> b) -> Maybe m a -> Maybe m b`，這看起來並不合理，因為 `Maybe` 只接受一個型別參數。

總之，`fmap` 的實作是很簡單的。如果一個空值是 `Nothing`，那他就會回傳 `Nothing`。如果我們 map over 一個空的盒子，我們就會得到一個空的盒子。就像我們 map over 一個空的 List，那我們就會得到一個空的 List。如果他不是一個空值，而是包在 `Just` 中的某個值，那我們就會套用在包在 `Just` 中的值。

```haskell
ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") (Just "Something serious.")
Just "Something serious. HEY GUYS IM INSIDE THE JUST"
ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") Nothing
Nothing
ghci> fmap (*2) (Just 200)
Just 400
ghci> fmap (*2) Nothing
Nothing
```

另外 `Tree a` 的型別也可以被 map over 且被定義成 `Functor` 的一個 instance。他可以被想成是一個盒子，而 `Tree` 的型別構造子也剛好接受單一一個型別參數。如果你把 `fmap` 看作是一個特別為 `Tree` 寫的函數，他的型別宣告會長得像這樣 `(a -> b) -> Tree a -> Tree b`。不過我們在這邊會用到遞迴。map over 一棵空的樹會得到一棵空的樹。map over 一棵非空的樹會得到一棵被函數映射過的樹，他的 root 會先被映射，然後左右子樹都分別遞迴地被函數映射。

```haskell
instance Functor Tree where
    fmap f EmptyTree = EmptyTree
    fmap f (Node x leftsub rightsub) =
        Node (f x) (fmap f leftsub) (fmap f rightsub)
```

```haskell
ghci> fmap (*2) EmptyTree
EmptyTree
ghci> fmap (*4) (foldr treeInsert EmptyTree [5,7,3,2,1,7])
Node 28 (Node 4 EmptyTree (Node 8 EmptyTree (Node 12 EmptyTree (Node 20 EmptyTree EmptyTree)))) EmptyTree
```

那 `Either a b` 又如何？他可以是一個 functor 嗎？`Functor` 限制型別構造子只能接受一個型別參數，但 `Either` 卻接受兩個。聰明的你會想到我可以 partial apply `Either`，先餵給他一個參數，並把另一個參數當作 free parameter。來看看 `Either a` 在標準函式庫中是如何被定義的：

```haskell
instance Functor (Either a) where
    fmap f (Right x) = Right (f x)
    fmap f (Left x) = Left x
```

我們在這邊做了些什麼？你可以看到我們把 `Either a` 定義成一個 instance，而不是 `Either`。那是因為 `Either a` 是一個接受單一型別參數的型別構造子，而 `Either` 則接受兩個。如果 `fmap` 是針對 `Either a`，那他的型別宣告就會像是 `(b -> c) -> Either a b -> Either a c`，他又等價於 `(b -> c) -> (Either a) b -> (Either a) c`。在實作中，我們碰到一個 `Right` 的時候會做 `map`，但在碰到 `Left` 的時候卻不這樣做，為什麼呢？如果我們回頭看看 `Either a b` 是怎麼定義的：

```haskell
data Either a b = Left a | Right b
```

如果我們希望對他們兩個都做 `map` 的動作，那 `a` 跟 `b` 必須要是相同的型別。也就是說，如果我們的函數是接受一個字串然後回傳另一個字串，而且 `b` 是字串，`a` 是數字，這樣的情形是不可行的。而且從觀察 `fmap` 的型別也可以知道，當他運作在 `Either` 上的時候，第一個型別參數必須固定，而第二個則可以改變，而其中第一個參數正好就是 `Left` 用的。

我們持續用盒子的比喻也仍然貼切，我們可以把 `Left` 想做是空的盒子在他旁邊寫上錯誤訊息，說明為什麼他是空的。

在 `Data.Map` 中的 Map 也可以被定義成 functor，像是 `Map k v` 的情況下，`fmap` 可以用 `v -> v'` 這樣一個函數來 map over `Map k v`，並回傳 `Map k v'`。

注意到 `'` 在這邊並沒有特別的意思，他只是用來表示他跟另一個東西有點像，只有一點點差別而已。

你可以自己試試看把 `Map k` 變成 `Functor` 的一個 instance。

看過了 `Functor` 這個 typeclass，我們知道 typeclass 可以拿來代表高階的概念。我們也練習過不少 partially applying type 跟定義 instance。在下幾章中，我們也會看到 functor 必須要遵守的定律。

還有一件事就是 functor 應該要遵守一些定律，這樣他們的一些性質才能被保證。如果我們用 `fmap (+1)` 來 map over `[1,2,3,4]`，我們會期望結果會是 `[2,3,4,5]` 而不是反過來變成 `[5,4,3,2]`。如果我們使用 `fmap (\a -> a)`來 map over 一個 list，我們會期待拿回相同的結果。例如說，如果我們給 `Tree` 定義了錯誤的 functor instance，對 tree 使用 `fmap` 可能會導致二元搜尋樹的性質喪失，也就是在 root 左邊的節點不再比 root 小，在 root 右邊的節點不再比 root 大。我們在下面幾章會多談 functor laws。

## Kind

型別構造子接受其他型別作為他的參數，來構造出一個具體型別。這樣的行為會讓我們想到函數，也是接受一個值當作參數，並回傳另一個值。我們也看過型別構造子可以 partially apply （`Either String` 是一個型別構造子，他接受一個型別來構造出一個具體型別，就像 `Either String Int`）。這些都是函數能辦到的事。在這個章節中，對於型別如何被套用到型別構造子上，我們會來看一下正式的定義。就像我們之前是用函數的型別來定義出函數是如何套用值的。如果你看不懂的話，你可以跳過這一章，這不會影響你後續的閱讀。然而如果你搞懂的話，你會對於型別系統有更進一步的了解。

像是 `3`,`"YEAH"` 或是 `takeWhile` 的值他們都有自己的型別（函數也是值的一種，我們可以把他們傳來傳去）型別是一個標籤，值會把他帶著，這樣我們就可以推測出他的性質。但型別也有他們自己的標籤，叫做 kind。kind 是型別的型別。雖然聽起來有點玄妙，不過他的確是個有趣的概念。

那kind可以拿來做什麼呢？我們可以在 ghci 中用 `:k` 來得知一個型別的 kind。

```haskell
ghci> :k Int
Int :: *
```

一個星星代表的是什麼意思？一個 `*` 代表這個型別是具體型別。一個具體型別是沒有任何型別參數，而值只能屬於具體型別。而 `*` 的讀法叫做 star 或是 type。

我們再看看 `Maybe` 的 kind：

```haskell
ghci> :k Maybe
Maybe :: * -> *
```

`Maybe` 的型別構造子接受一個具體型別（像是 `Int`）然後回傳一個具體型別，像是 `Maybe Int`。這就是 kind 告訴我們的資訊。就像 `Int -> Int` 代表這個函數接受 `Int` 並回傳一個 `Int`。`* -> *` 代表這個型別構造子接受一個具體型別並回傳一個具體型別。我們再來對 `Maybe` 套用型別參數後再看看他的 kind 是什麼：

```haskell
ghci> :k Maybe Int
Maybe Int :: *
```

正如我們預期的。我們對 `Maybe` 套用了型別參數後會得到一個具體型別（這就是 `* -> *` 的意思）這跟 `:t isUpper` 還有 `:t isUpper 'A'` 的差別有點類似。`isUpper` 的型別是 `Char -> Bool` 而 `isUpper 'A'` 的型別是 `Bool`。而這兩種型別，都是 `*` 的 kind。

我們對一個型別使用 `:k` 來得到他的 kind。就像我們對值使用 `:t` 來得到的他的型別一樣。就像我們先前說的，型別是值的標籤，而 kind 是型別的標籤。

我們再來看看其他的 kind

```haskell
ghci> :k Either
Either :: * -> * -> *
```

這告訴我們 `Either` 接受兩個具體型別作為參數，並構造出一個具體型別。他看起來也像是一個接受兩個參數並回傳值的函數型別。型別構造子是可以做 curry 的，所以我們也能 partially apply。

```haskell
ghci> :k Either String
Either String :: * -> *
ghci> :k Either String Int
Either String Int :: *
```

當我們希望定義 `Either` 成為 `Functor` 的 instance 的時候，我們必須先 partial apply，因為 `Functor` 預期有一個型別參數，但 `Either` 卻有兩個。也就是說，`Functor` 希望型別的 kind 是 `* -> *`，而我們必須先 partial apply `Either` 來得到 kind `* -> *`，而不是最開始的 `* -> * -> *`。我們再來看看 `Functor` 的定義

```haskell
class Functor f where
    fmap :: (a -> b) -> f a -> f b
```

我們看到 `f` 型別變數是接受一個具體型別且構造出一個具體型別的型別。 知道他構造出具體型別是因為是作為函數參數的型別。 從那裡我們可以推測出一個型別要是屬於 `Functor` 必須是 `* -> *` kind。

現在我們來練習一下。來看看下面這個新定義的 typeclass。

```haskell
class Tofu t where
    tofu :: j a -> t a j
```

這看起來很怪。我們幹嘛要為這個奇怪的 typeclass 定義 instance？我們可以來看看他的 kind 是什麼？由於 `j a` 被當作 `tofu` 這個函數的參數的型別，所以 `j a` 一定是 `*` kind。我們假設 `a` 是 `*` kind，那 `j` 就會是 `* -> *` 的 kind。我們看到 `t` 由於是函數的回傳值，一定是接受兩個型別參數的型別。而知道 `a` 是 `*`，`j` 是 `* -> *`，我們可以推測出 `t`是`* -> (* -> *) -> *`。也就是說他接受一個具體型別 `a`，一個接受單一參數的型別構造子 `j`，然後產生出一個具體型別。

我們再來定義出一個型別具有 `* -> (* -> *) -> *` 的 kind，下面是一種定義的方法：

```haskell
data Frank a b  = Frank {frankField :: b a} deriving (Show)
```

我們怎麼知道這個型別具有 `* -> (* -> *) -> *` 的 kind 呢？ADT 中的欄位是要來塞值的，所以他們必須是 `*` kind。我們假設 `a` 是 `*`，那 `b` 就是接受一個型別參數的 kind `* -> *`。現在我們知道 `a` 跟 `b` 的 kind 了，而他們又是 `Frank` 的型別參數，所以我們知道 `Frank` 會有 `* -> (* -> *) -> *` 的 kind。第一個 `*` 代表 `a`，而 `(* -> *)` 代表 `b`。我們構造些 `Frank` 的值並檢查他們的型別吧：

```haskell
ghci> :t Frank {frankField = Just "HAHA"}
Frank {frankField = Just "HAHA"} :: Frank [Char] Maybe
ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}
Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree
ghci> :t Frank {frankField = "YES"}
Frank {frankField = "YES"} :: Frank Char []
```

由於 `frankField` 具有 `a b` 的型別。他的值必定有一個類似的型別。他們可能是 `Just "HAHA"`，也就有 `Maybe [Char]` 的型別，或是他們可能是 `['Y','E','S']`，他的型別是 `[Char]`。（如果我們是用自己定義的 List 型別的話，那就會是 `List Char`）。我們看到 `Frank` 值的型別對應到 `Frank` 的 kind。`[Char]` 具有 `*` 的 kind，而 `Maybe` 則是 `* -> *`。由於結果必須是個值，也就是他必須要是具體型別，因使他必須 fully applied，因此每個 `Frank blah blaah` 的值都會是 `*` 的 kind。

要把 `Frank` 定義成 `Tofu` 的 instance 也是很簡單。我們看到 `tofu` 接受 `j a`（例如 `Maybe Int`）並回傳 `t a j`。所以我們將 `Frank` 代入 `t`，就得到 `Frank Int Maybe`。

```haskell
instance Tofu Frank where
    tofu x = Frank x
```

```haskell
ghci> tofu (Just 'a') :: Frank Char Maybe
Frank {frankField = Just 'a'}
ghci> tofu ["HELLO"] :: Frank [Char] []
Frank {frankField = ["HELLO"]}
```

這並不是很有用，但讓我們做了不少型別的練習。再來看看下面的型別：

```haskell
data Barry t k p = Barry { yabba :: p, dabba :: t k }
```

我們想要把他定義成 `Functor` 的 instance。`Functor` 希望是 `* -> *` 的型別，但 `Barry` 並不是那種 kind。那 `Barry` 的 kind 是什麼呢？我們可以看到他接受三個型別參數，所以會是 `something -> something -> something -> *`。`p` 是一個具體型別因此是 `*`。至於 `k`，我們假設他是 `*`，所以 `t` 會是 `* -> *`。現在我們把這些代入 `something`，所以 kind 就變成 `(* -> *) -> * -> * -> *`。我們用 ghci 來檢查一下。

```haskell
ghci> :k Barry
Barry :: (* -> *) -> * -> * -> *
```

我們猜對了！現在要把這個型別定義成 `Functor`，我們必須先 partially apply 頭兩個型別參數，這樣我們就會是 `* -> *` 的 kind。這代表 instance 定義會是 `instance Functor (Barry a b) where`。如果我們看 `fmap` 針對 `Barry` 的型別，也就是把 `f` 代換成 `Barry c d`，那就會是 `fmap :: (a -> b) -> Barry c d a -> Barry c d b`。第三個 `Barry` 的型別參數是對於任何型別，所以我們並不牽扯進他。

```haskell
instance Functor (Barry a b) where
    fmap f (Barry {yabba = x, dabba = y}) = Barry {yabba = f x, dabba = y}
```

我們把 `f` map 到第一個欄位。

在這一個章節中，我們看到型別參數是怎麼運作的，以及正如我們用型別來定義出函數的參數，我們也用 kind 是來定義他。我們看到函數跟型別構造子有許多彼此相像的地方。然而他們是兩個完全不同的東西。當我們在寫一般實用的 Haskell 程式時，你幾乎不會碰到需要動到 kind 的東西，也不需要動腦去推敲 kind。通常你只需要在定義 instance 時 partially apply 你自己的 `* -> *` 或是 `*` 型別，但知道背後運作的原理也是很好的。知道型別本身也有自己的型別也是很有趣的。如果你實在不懂這邊講的東西，也可以繼續閱讀下去。但如果你能理解，那你就會理解 Haskell 型別系統的一大部份。


# ch09


# 輸入與輸出

![](/files/-LjUmt6pzI_OLoQCVpcD)

我們已經說明了 Haskell 是一個純粹函數式語言。雖說在命令式語言中我們習慣給電腦執行一連串指令，在函數式語言中我們是用定義東西的方式進行。在 Haskell 中，一個函數不能改變狀態，像是改變一個變數的內容。（當一個函數會改變狀態，我們說這函數是有副作用的。）在 Haskell 中函數唯一可以做的事是根據我們給定的參數來算出結果。如果我們用同樣的參數呼叫兩次同一個函數，它會回傳相同的結果。儘管這從命令式語言的角度來看是蠻大的限制，我們已經看過它可以達成多麼酷的效果。在一個命令式語言中，程式語言沒辦法給你任何保證在一個簡單如打印出幾個數字的函數不會同時燒掉你的房子，綁架你的狗並刮傷你車子的烤漆。例如，當我們要建立一棵二元樹的時候，我們並不插入一個節點來改變原有的樹。由於我們無法改變狀態，我們的函數實際上回傳了一棵新的二元樹。

函數無法改變狀態的好處是它讓我們促進了我們理解程式的容易度，但同時也造成了一個問題。假如說一個函數無法改變現實世界的狀態，那它要如何打印出它所計算的結果？畢竟要告訴我們結果的話，它必須要改變輸出裝置的狀態（譬如說螢幕），然後從螢幕傳達到我們的腦，並改變我們心智的狀態。

不要太早下結論，Haskell 實際上設計了一個非常聰明的系統來處理有副作用的函數，它漂亮地將我們的程式區分成純粹跟非純粹兩部分。非純粹的部分負責跟鍵盤還有螢幕溝通。有了這區分的機制，在跟外界溝通的同時，我們還是能夠有效運用純粹所帶來的好處，像是惰性求值、容錯性跟模組性。

## Hello, world!

![](/files/-LjUmt6tk6b7iTq7FCjo)

到目前為止我們都是將函數載入 GHCi 中來測試，像是標準函式庫中的一些函式。但現在我們要做些不一樣的，寫一個真實跟世界互動的 Haskell 程式。當然不例外，我們會來寫個 "hello world"。

現在，我們把下一行打到你熟悉的編輯器中

```haskell
main = putStrLn "hello, world"
```

我們定義了一個 `main`，並在裡面以 `"hello, world"` 為參數呼叫了 `putStrLn`。看起來沒什麼大不了，但不久你就會發現它的奧妙。把這程式存成 `helloworld.hs`。

現在我們將做一件之前沒做過的事：編譯你的程式。打開你的終端並切換到包含 `helloworld.hs` 的目錄，並輸入下列指令。

```haskell
$ ghc --make helloworld
[1 of 1] Compiling Main                 ( helloworld.hs, hellowowlrd.o )
Linking helloworld ...
```

順利的話你就會得到如上的訊息，接著你便可以執行你的程式 `./helloworld`

```haskell
$ ./helloworld
hello, world
```

這就是我們第一個編譯成功並打印出字串到螢幕的程式。很簡單吧。

讓我們來看一下我們究竟做了些什麼，首先來看一下 `putStrLn` 函數的型態：

```haskell
ghci> :t putStrLn
putStrLn :: String -> IO ()
ghci> :t putStrLn "hello, world"
putStrLn "hello, world" :: IO ()
```

我們可以這麼解讀 `putStrLn` 的型態：`putStrLn` 接受一個字串並回傳一個 I/O action，這 I/O action 包含了 `()` 的型態。（即空的 tuple，或者是 unit 型態）。一個 I/O action 是一個會造成副作用的動作，常是指讀取輸入或輸出到螢幕，同時也代表會回傳某些值。在螢幕打印出幾個字串並沒有什麼有意義的回傳值可言，所以這邊用一個 `()` 來代表。

那究竟 I/O action 會在什麼時候被觸發呢？這就是 `main` 的功用所在。一個 I/O action 會在我們把它綁定到 `main` 這個名字並且執行程式的時候觸發。

把整個程式限制在只能有一個 I/O action 看似是個極大的限制。這就是為什麼我們需要 do 表示法來將所有 I/O action 綁成一個。來看看下面這個例子。

```haskell
main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")
```

新的語法，有趣吧！它看起來就像一個命令式的程式。如果你編譯並執行它，它便會照你預期的方式執行。我們寫了一個 do 並且接著一連串指令，就像寫個命令式程式一般，每一步都是一個 I/O action。將所有 I/O action 用 do 綁在一起變成了一個大的 I/O action。這個大的 I/O action 的型態是 `IO ()`，這完全是由最後一個 I/O action 所決定的。

這就是為什麼 `main` 的型態永遠都是 `main :: IO something`，其中 `something` 是某個具體的型態。按照慣例，我們通常不會把 `main` 的型態在程式中寫出來。

另一個有趣的事情是第三行 `name <- getLine`。它看起來像是從輸入讀取一行並存到一個變數 `name` 之中。真的是這樣嗎？我們來看看 `getLine` 的型態吧

```haskell
ghci> :t getLine
getLine :: IO String
```

![](/files/-LjUmt6wFoUi8l7q7xwD)

我們可以看到 `getLine` 是一個回傳 `String` 的 I/O action。因為它會等使用者輸入某些字串，這很合理。那 `name <- getLine` 又是如何？你能這樣解讀它：執行一個 I/O action `getLine` 並將它的結果綁定到 `name` 這個名字。`getLine` 的型態是 `IO String`，所以 `name` 的型態會是 `String`。你能把 I/O action 想成是一個長了腳的盒子，它會跑到真實世界中替你做某些事，像是在牆壁上塗鴉，然後帶回來某些資料。一旦它帶了某些資料給你，打開盒子的唯一辦法就是用 `<-`。而且如果我們要從 I/O action 拿出某些資料，就一定同時要在另一個 I/O action 中。這就是 Haskell 如何漂亮地分開純粹跟不純粹的程式的方法。`getLine` 在這樣的意義下是不純粹的，因為執行兩次的時候它沒辦法保證會回傳一樣的值。這也是為什麼它需要在一個 `IO` 的型態建構子中，那樣我們才能在 I/O action 中取出資料。而且任何一段程式一旦依賴著 I/O 資料的話，那段程式也會被視為 I/O code。

但這不表示我們不能在純粹的程式碼中使用 I/O action 回傳的資料。只要我們綁定它到一個名字，我們便可以暫時地使用它。像在 `name <- getLine` 中 `name` 不過是一個普通字串，代表在盒子中的內容。我們能將這個普通的字串傳給一個極度複雜的函數，並回傳你一生會有多少財富。像是這樣：

```haskell
main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn $ "Read this carefully, because this is your future: " ++ tellFortune name
```

`tellFortune` 並不知道任何 I/O 有關的事，它的型態只不過是 `String -> String`。

再來看看這段程式碼吧，他是合法的嗎?

```haskell
nameTag = "Hello, my name is " ++ getLine
```

如果你回答不是，恭喜你。如果你說是，你答錯了。這麼做不對的理由是 `++` 要求兩個參數都必須是串列。他左邊的參數是 `String`，也就是 `[Char]`。然而 `getLine` 的型態是 `IO String`。你不能串接一個字串跟 I/O action。我們必須先把 `String` 的值從 I/O action 中取出，而唯一可行的方法就是在 I/O action 中使用 `name <- getLine`。如果我們需要處理一些非純粹的資料，那我們就要在非純粹的環境中做。所以我們最好把 I/O 的部分縮減到最小的比例。

每個 I/O action 都有一個值封裝在裡面。這也是為什麼我們之前的程式可以這麼寫：

```haskell
main = do
    foo <- putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")
```

然而，`foo` 只會有一個 `()` 的值，所以綁定到 `foo` 這個名字似乎是多餘的。另外注意到我們並沒有綁定最後一行的 `putStrLn` 給任何名字。那是因為在一個 do block 中，最後一個 action 不能綁定任何名字。我們在之後講解 Monad 的時候會說明為什麼。現在你可以先想成 do block 會自動從最後一個 action 取出值並綁定給他的結果。

除了最後一行之外，其他在 do 中沒有綁定名字的其實也可以寫成綁定的形式。所以 `putStrLn "BLAH"` 可以寫成 `_ <- putStrLn "BLAH"`。但這沒什麼實際的意義，所以我們寧願寫成 `putStrLn something`。

初學者有時候會想錯

```haskell
    name = getLine
```

以為這行會讀取輸入並給他綁定一個名字叫 `name` 但其實只是把 `getLine` 這個 I/O action 指定一個名字叫 `name` 罷了。記住，要從一個 I/O action 中取出值，你必須要在另一個 I/O action 中將他用 `<-` 綁定給一個名字。

I/O actions 只會在綁定給 `main` 的時候或是在另一個用 do 串起來的 I/O action 才會執行。你可以用 do 來串接 I/O actions，再用 do 來串接這些串接起來的 I/O actions。不過只有最外面的 I/O action 被指定給 main 才會觸發執行。

喔對，其實還有另外一個情況。就是在 GHCi 中輸入一個 I/O action 並按下 Enter 鍵，那也會被執行

```haskell
ghci> putStrLn "HEEY"
HEEY
```

就算我們只是在 GHCi 中打幾個數字或是呼叫一個函數，按下 Enter 就會計算它並呼叫 `show`，再用 `putStrLn` 將字串打印出在終端上。

還記得 let binding 嗎？如果不記得，回去溫習一下這個章節。它們的形式是 `let bindings in expression`，其中 `bindings` 是 expression 中的名字、`expression` 則是被運用到這些名字的算式。我們也提到了 list comprehensions 中，`in` 的部份不是必需的。你能夠在 do blocks 中使用 let bindings 如同在 list comprehensions 中使用它們一樣，像這樣：

```haskell
import Data.Char

main = do
    putStrLn "What's your first name?"
    firstName <- getLine
    putStrLn "What's your last name?"
    lastName <- getLine
    let bigFirstName = map toUpper firstName
        bigLastName = map toUpper lastName
    putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?"
```

注意我們是怎麼編排在 do block 中的 I/O actions，也注意到我們是怎麼編排 let 跟其中的名字的，由於對齊在 Haskell 中並不會被無視，這麼編排才是好的習慣。我們的程式用 `map toUpper firstName` 將 `"John"` 轉成大寫的 `"JOHN"`，並將大寫的結果綁定到一個名字上，之後在輸出的時候參考到了這個名字。

你也許會問究竟什麼時候要用 `<-`，什麼時候用 let bindings？記住，`<-` 是用來運算 I/O actions 並將他的結果綁定到名稱。而 `map toUpper firstName` 並不是一個 I/O action。他只是一個純粹的 expression。所以總結來說，當你要綁定 I/O actions 的結果時用 `<-`，而對於純粹的 expression 使用 let bindings。對於錯誤的 `let firstName = getLine`，我們只不過是把 `getLine` 這個 I/O actions 給了一個不同的名字罷了。最後還是要用 `<-` 將結果取出。

現在我們來寫一個會一行一行不斷地讀取輸入，並將讀進來的字反過來輸出到螢幕上的程式。程式會在輸入空白行的時候停止。

```haskell
main = do
    line <- getLine
    if null line
        then return ()
        else do
            putStrLn $ reverseWords line
            main

reverseWords :: String -> String
reverseWords = unwords . map reverse . words
```

在分析這段程式前，你可以執行看看來感受一下程式的運行。

首先，我們來看一下 `reverseWords`。他不過是一個普通的函數，假如接受了個字串 `"hey there man"`，他會先呼叫 `words` 來產生一個字的串列 `["hey", "there", "man"]`。然後用 `reverse` 來 map 整個串列，得到 `["yeh", "ereht", "nam"]`，接著用 `unwords` 來得到最終的結果 `"yeh ereht nam"`。這些用函數合成來簡潔的表達。如果沒有用函數合成，那就會寫成醜醜的樣子 `reverseWords st = unwords (map reverse (words st))`

那 `main` 又是怎麼一回事呢？首先，我們用 `getLine` 從終端讀取了一行，並把這行輸入取名叫 `line`。然後接著一個條件式 expression。記住，在 Haskell 中 if 永遠要伴隨一個 else，這樣每個 expression 才會有值。當 if 的條件是 true （也就是輸入了一個空白行），我們便執行一個 I/O action，如果 if 的條件是 false，那 else 底下的 I/O action 被執行。這也就是說當 if 在一個 I/O do block 中的時候，長的樣子是 `if condition then I/O action else I/O action`。

我們首先來看一下在 else 中發生了什麼事。由於我們在 else 中只能有一個 I/O action，所以我們用 do 來將兩個 I/O actions 綁成一個，你可以寫成這樣：

```haskell
else (do
    putStrLn $ reverseWords line
    main)
```

這樣可以明顯看到整個 do block 可以看作一個 I/O action，只是比較醜。但總之，在 do block 裡面，我們依序呼叫了 `getLine` 以及 `reverseWords`，在那之後，我們遞迴呼叫了 `main`。由於 main 也是一個 I/O action，所以這不會造成任何問題。呼叫 `main` 也就代表我們回到程式的起點。

那假如 `null line` 的結果是 true 呢？也就是說 then 的區塊被執行。我們看一下區塊裡面有 `then return ()`。如果你是從 C、Java 或 Python 過來的，你可能會認為 `return` 不過是作一樣的事情便跳過這一段。但很重要的： `return` 在 Hakell 裡面的意義跟其他語言的 `return` 完全不同！他們有相同的樣貌，造成了許多人搞錯，但確實他們是不一樣的。在命令式語言中，`return` 通常結束 method 或 subroutine 的執行，並且回傳某個值給呼叫者。在 Haskell 中，他的意義則是利用某個 pure value 造出 I/O action。用之前盒子的比喻來說，就是將一個 value 裝進箱子裡面。產生出的 I/O action 並沒有作任何事，只不過將 value 包起來而已。所以在 I/O 的情況下來說，`return "haha"` 的型態是 `IO String`。將 pure value 包成 I/O action 有什麼實質意義呢？為什麼要弄成 `IO` 包起來的值？這是因為我們一定要在 else 中擺上某些 I/O action，所以我們才用 `return ()` 做了一個沒作什麼事情的 I/O action。

在 I/O do block 中放一個 `return` 並不會結束執行。像下面這個程式會執行到底。

```haskell
main = do
    return ()
    return "HAHAHA"
    line <- getLine
    return "BLAH BLAH BLAH"
    return 4
    putStrLn line
```

所有在程式中的 `return` 都是將 value 包成 I/O actions，而且由於我們沒有將他們綁定名稱，所以這些結果都被忽略。我們能用 `<-` 與 `return` 來達到綁定名稱的目的。

```haskell
main = do
    a <- return "hell"
    b <- return "yeah!"
    putStrLn $ a ++ " " ++ b
```

可以看到 `return` 與 `<-` 作用相反。`return` 把 value 裝進盒子中，而 `<-` 將 value 從盒子拿出來，並綁定一個名稱。不過這麼做是有些多餘，因為你可以用 let bindings 來綁定

```haskell
main = do
    let a = "hell"
        b = "yeah"
    putStrLn $ a ++ " " ++ b
```

在 I/O do block 中需要 `return` 的原因大致上有兩個：一個是我們需要一個什麼事都不做的 I/O action，或是我們不希望這個 do block 形成的 I/O action 的結果值是這個 block 中的最後一個 I/O action，我們希望有一個不同的結果值，所以我們用 `return` 來作一個 I/O action 包了我們想要的結果放在 do block 的最後。

在我們接下去講檔案之前，讓我們來看看有哪些實用的函數可以處理 I/O。

`putStr` 跟 `putStrLn` 幾乎一模一樣，都是接受一個字串當作參數，並回傳一個 I/O action 打印出字串到終端上，只差在 `putStrLn` 會換行而 `putStr` 不會罷了。

```haskell
main = do putStr "Hey, "
          putStr "I'm "
          putStrLn "Andy!"
```

```haskell
$ runhaskell putstr_test.hs
Hey, I'm Andy!
```

他的 type signature 是 `putStr :: String -> IO ()`，所以是一個包在 I/O action 中的 unit。也就是空值，沒有辦法綁定他。

`putChar` 接受一個字元，並回傳一個 I/O action 將他打印到終端上。

```haskell
main = do putChar 't'
          putChar 'e'
          putChar 'h'
```

```haskell
$ runhaskell putchar_test.hs
teh
```

`putStr` 實際上就是 `putChar` 遞迴定義出來的。`putStr` 的邊界條件是空字串，所以假設我們打印一個空字串，那他只是回傳一個什麼都不做的 I/O action，像 `return ()`。如果打印的不是空字串，那就先用 `putChar` 打印出字串的第一個字元，然後再用 `putStr` 打印出字串剩下部份。

```haskell
putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do
    putChar x
    putStr xs
```

看看我們如何在 I/O 中使用遞迴，就像我們在 pure code 中所做的一樣。先定義一個邊界條件，然後再思考剩下如何作。

`print` 接受任何是 `Show` typeclass 的 instance 的型態的值，這代表我們知道如何用字串表示他，呼叫 `show` 來將值變成字串然後將其輸出到終端上。基本上，他就是 `putStrLn . show`。首先呼叫 `show` 然後把結果餵給 `putStrLn`，回傳一個 I/O action 打印出我們的值。

```haskell
main = do print True
          print 2
          print "haha"
          print 3.2
          print [3,4,3]
```

```haskell
$ runhaskell print_test.hs
True
2
"haha"
3.2
[3,4,3]
```

就像你看到的，這是個很方便的函數。還記得我們提到 I/O actions 只有在 `main` 中才會被執行以及在 GHCI 中運算的事情嗎？當我們用鍵盤打了些值，像 `3` 或 `[1,2,3]` 並按下 Enter，GHCI 實際上就是用了 `print` 來將這些值輸出到終端。

```haskell
ghci> 3
3
ghci> print 3
3
ghci> map (++"!") ["hey","ho","woo"]
["hey!","ho!","woo!"]
ghci> print (map (++"!") ["hey", "ho", "woo"])
["hey!","ho!","woo!"]
```

當我們需要打印出字串，我們會用 `putStrLn`，因為我們不想要周圍有引號，但對於輸出值來說，`print` 才是最常用的。

`getChar` 是一個從輸入讀進一個字元的 I/O action，因此他的 type signature 是 `getChar :: IO Char`，代表一個 I/O action 的結果是 `Char`。注意由於緩衝區的關係，只有當 Enter 被按下的時候才會觸發讀取字元的行為。

```haskell
main = do
    c <- getChar
    if c /= ' '
        then do
            putChar c
            main
        else return ()
```

這程式看起來像是讀取一個字元並檢查他是否為一個空白。如果是的話便停止，如果不是的話便打印到終端上並重複之前的行為。在某種程度上來說也不能說錯，只是結果不如你預期而已。來看看結果吧。

```haskell
$ runhaskell getchar_test.hs
hello sir
hello
```

上面的第二行是輸入。我們輸入了 `hello sir` 並按下了 Enter。由於緩衝區的關係，程式是在我們按了 Enter 後才執行而不是在某個輸入字元的時候。一旦我們按下了 Enter，那他就把我們直到目前輸入的一次做完。

`when` 這函數可以在 `Control.Monad` 中找到他 (你必須 `import Contorl.Monad` 才能使用他)。他在一個 do block 中看起來就像一個控制流程的 statement，但實際上他的確是一個普通的函數。他接受一個 boolean 值跟一個 I/O action。如果 boolean 值是 `True`，便回傳我們傳給他的 I/O action。如果 boolean 值是 `False`，便回傳 `return ()`，即什麼都不做的 I/O action。我們接下來用 `when` 來改寫我們之前的程式。

```haskell
import Control.Monad

main = do
    c <- getChar
    when (c /= ' ') $ do
        putChar c
        main
```

就像你看到的，他可以將 `if something then do some I/O action else return ()` 這樣的模式封裝起來。

`sequence` 接受一串 I/O action，並回傳一個會依序執行他們的 I/O action。運算的結果是包在一個 I/O action 的一連串 I/O action 的運算結果。他的 type signature 是 `sequence :: [IO a] -> IO [a]`

```haskell
main = do
    a <- getLine
    b <- getLine
    c <- getLine
    print [a,b,c]
```

其實可以寫成

```haskell
main = do
    rs <- sequence [getLine, getLine, getLine]
    print rs
```

所以 `sequence [getLine, getLine, getLine]` 作成了一個執行 `getLine` 三次的 I/O action。如果我們對他綁定一個名字，結果便是這串結果的串列。也就是說，三個使用者輸入的東西組成的串列。

一個常見的使用方式是我們將 `print` 或 `putStrLn` 之類的函數 map 到串列上。`map print [1,2,3,4]` 這個動作並不會產生一個 I/O action，而是一串 I/O action，就像是 `[print 1, print 2, print 3, print 4]`。如果我們將一串 I/O action 變成一個 I/O action，我們必須用 `sequence`

```haskell
ghci> sequence (map print [1,2,3,4,5])
1
2
3
4
5
[(),(),(),(),()]
```

那 `[(),(),(),(),()]` 是怎麼回事？當我們在 GHCI 中運算 I/O action，他會被執行並把結果打印出來，唯一例外是結果是 `()` 的時候不會被打印出。這也是為什麼 `putStrLn "hehe"` 在 GHCI 中只會打印出 `hehe`（因為 `putStrLn "hehe"` 的結果是 `()`）。但當我們使用 `getLine` 時，由於 `getLine` 的型態是 `IO String`，所以結果會被打印出來。

由於對一個串列 map 一個回傳 I/O action 的函數，然後再 sequence 他這個動作太常用了。所以有一些函數在函式庫中 `mapM` 跟 `mapM_`。`mapM` 接受一個函數跟一個串列，將對串列用函數 map 然後 sequence 結果。`mapM_` 也作同樣的事，只是他把運算的結果丟掉而已。在我們不關心 I/O action 結果的情況下，`mapM_` 是最常被使用的。

```haskell
ghci> mapM print [1,2,3]
1
2
3
[(),(),()]
ghci> mapM_ print [1,2,3]
1
2
3
```

`forever` 接受一個 I/O action 並回傳一個永遠作同一件事的 I/O action。你可以在 `Control.Monad` 中找到他。下面的程式會不斷地要使用者輸入些東西，並把輸入的東西轉成大寫輸出到螢幕上。

```haskell
import Control.Monad
import Data.Char

main = forever $ do
    putStr "Give me some input: "
    l <- getLine
    putStrLn $ map toUpper l
```

在 `Control.Monad` 中的 `forM` 跟 `mapM` 的作用一樣，只是參數的順序相反而已。第一個參數是串列，而第二個則是函數。這有什麼用？在一些有趣的情況下還是有用的：

```haskell
import Control.Monad

main = do
    colors <- forM [1,2,3,4] (\a -> do
        putStrLn $ "Which color do you associate with the number " ++ show a ++ "?"
        color <- getLine
        return color)
    putStrLn "The colors that you associate with 1, 2, 3 and 4 are: "
    mapM putStrLn colors
```

`(\a -> do ...)` 是接受一個數字並回傳一個 I/O action 的函數。我們必須用括號括住他，不然 lambda 會貪心 match 的策略會把最後兩個 I/O action 也算進去。注意我們在 do block 裡面 `return color`。我們那麼作是讓 do block 的結果是我們選的顏色。實際上我們並不需那麼作，因為 `getLine` 已經達到我們的目的。先 `color <- getLine` 再 `return color` 只不過是把值取出再包起來，其實是跟 `getLine` 效果相當。`forM` 產生一個 I/O action，我們把結果綁定到 `colors` 這名稱。`colors` 是一個普通包含字串的串列。最後，我們用 `mapM putStrLn colors` 打印出所有顏色。

你可以把 `forM` 的意思想成將串列中的每個元素作成一個 I/O action。至於每個 I/O action 實際作什麼就要看原本的元素是什麼。然後，執行這些 I/O action 並將結果綁定到某個名稱上。或是直接將結果忽略掉。

```haskell
$ runhaskell from_test.hs
Which color do you associate with the number 1?
white
Which color do you associate with the number 2?
blue
Which color do you associate with the number 3?
red
Which color do you associate with the number 4?
orange
The colors that you associate with 1, 2, 3 and 4 are:
white
blue
red
orange
```

其實我們也不是一定要用到 `forM`，只是用了 `forM` 程式會比較容易理解。正常來講是我們需要在 map 跟 sequence 的時候定義 I/O action 的時候使用 `forM`，同樣地，我們也可以將最後一行寫成 `forM colors putStrLn`。

在這一節，我們學會了輸入與輸出的基礎。我們也了解了什麼是 I/O action，他們是如何幫助我們達成輸入與輸出的目的。這邊重複一遍，I/O action 跟其他 Haskell 中的 value 沒有兩樣。我們能夠把他當參數傳給函式，或是函式回傳 I/O action。他們特別之處在於當他們是寫在 `main` 裡面或 GHCI 裡面的時候，他們會被執行，也就是實際輸出到你螢幕或輸出音效的時候。每個 I/O action 也能包著一個從真實世界拿回來的值。

不要把像是 `putStrLn` 的函式想成接受字串並輸出到螢幕。要想成一個函式接受字串並回傳一個 I/O action。當 I/O action 被執行的時候，會漂亮地打印出你想要的東西。

## 檔案與字符流

![](/files/-LjUmt74AgbsRlZBH6jg)

`getChar` 是一個讀取單一字元的 I/O action。`getLine` 是一個讀取一行的 I/O action。這是兩個非常直覺的函式，多數程式語言也有類似這兩個函式的 statement 或 function。但現在我們來看看 *getContents*。`getContents` 是一個從標準輸入讀取直到 end-of-file 字元的 I/O action。他的型態是 `getContents :: IO String`。最酷的是 `getContents` 是惰性 I/O (Lazy I/O)。當我們寫了 `foo <- getContents`，他並不會馬上讀取所有輸入，將他們存在 memory 裡面。他只有當你真的需要輸入資料的時候才會讀取。

當我們需要重導一個程式的輸出到另一個程式的輸入時，`getContents` 非常有用。假設我們有下面一個文字檔：

```haskell
I'm a lil' teapot
What's with that airplane food, huh?
It's so small, tasteless
```

還記得我們介紹 `forever` 時寫的小程式嗎？會把所有輸入的東西轉成大寫的那一個。為了防止你忘記了，這邊再重複一遍。

```haskell
import Control.Monad
import Data.Char

main = forever $ do
    putStr "Give me some input: "
    l <- getLine
    putStrLn $ map toUpper l
```

將我們的程式存成 `capslocker.hs` 然後編譯他。然後用 Unix 的 Pipe 將文字檔餵給我們的程式。我們使用的是 GNU 的 cat，會將指定的檔案輸出到螢幕。

```haskell
$ ghc --make capslocker
[1 of 1] Compiling Main             ( capslocker.hs, capslocker.o )
Linking capslocker ...
$ cat haiku.txt
I'm a lil' teapot
What's with that airplane food, huh?
It's so small, tasteless
$ cat haiku.txt | ./capslocker
I'M A LIL' TEAPOT
WHAT'S WITH THAT AIRPLANE FOOD, HUH?
IT'S SO SMALL, TASTELESS
capslocker <stdin>: hGetLine: end of file
```

就如你看到的，我們是用 `|` 這符號來將某個程式的輸出 piping 到另一個程式的輸入。我們做的事相當於 run 我們的 capslocker，然後將 haiku 的內容用鍵盤打到終端上，最後再按 Ctrl-D 來代表 end-of-file。這就像執行 cat haiku.txt 後大喊，嘿，不要把內容打印到終端上，把內容塞到 capslocker！

我們用 `forever` 在做的事基本上就是將輸入經過轉換後變成輸出。用 `getContents` 的話可以讓我們的程式更加精鍊。

```haskell
import Data.Char

main = do
    contents <- getContents
    putStr (map toUpper contents)
```

我們將 `getContents` 取回的字串綁定到 `contents`。然後用 `toUpper` map 到整個字串後打印到終端上。記住字串基本上就是一串惰性的串列 (list)，同時 `getContents` 也是惰性 I/O，他不會一口氣讀入內容然後將內容存在記憶體中。實際上，他會一行一行讀入並輸出大寫的版本，這是因為輸出才是真的需要輸入的資料的時候。

```haskell
$ cat haiku.txt | ./capslocker
I'M A LIL' TEAPOT
WHAT'S WITH THAT AIRPLAN FOOD, HUH?
IT'S SO SMALL, TASTELESS
```

很好，程式運作正常。假如我們執行 capslocker 然後自己打幾行字呢？

```haskell
$ ./capslocker
hey ho
HEY HO
lets go
LETS GO
```

按下 Ctrl-D 來離開環境。就像你看到的，程式是一行一行將我們的輸入打印出來。當 `getContent` 的結果被綁定到 `contents` 的時候，他不是被表示成在記憶體中的一個字串，反而比較像是他有一天會是字串的一個承諾。當我們將 `toUpper` map 到 `contents` 的時候，便也是一個函數被承諾將會被 map 到內容上。最後 `putStr` 則要求先前的承諾說，給我一行大寫的字串吧。實際上還沒有任何一行被取出，所以便跟 `contents` 說，不如從終端那邊取出些字串吧。這才是 `getContents` 真正從終端讀入一行並把這一行交給程式的時候。程式便將這一行用 `toUpper` 處理並交給 `putStr`，`putStr` 則打印出他。之後 `putStr` 再說：我需要下一行。整個步驟便再重複一次，直到讀到 end-of-file 為止。

接著我們來寫個程式，讀取輸入，並只打印出少於十個字元的行。

```haskell
main = do
    contents <- getContents
    putStr (shortLinesOnly contents)

shortLinesOnly :: String -> String
shortLinesOnly input =
    let allLines = lines input
        shortLines = filter (\line -> length line < 10) allLines
        result = unlines shortLines
    in result
```

我們把 I/O 部份的程式碼弄得很短。由於程式的行為是接某些輸入，作些處理然後輸出。我們可以把他想成讀取輸入，呼叫一個函數，然後把函數的結果輸出。

`shortLinesOnly` 的行為是這樣：拿到一個字串，像是 `"short\nlooooooooooooooong\nshort again"`。這字串有三行，前後兩行比較短，中間一行很常。他用 `lines` 把字串分成 `["short", "looooooooooooooong", "short again"]`，並把結果綁定成 `allLines`。然後過濾這些字串，只有少於十個字元的留下，`["short", "short again"]`，最後用 `unlines` 把這些字串用換行接起來，形成 `"short\nshort again"`

```haskell
i'm short
so am i
i am a loooooooooong line!!!
yeah i'm long so what hahahaha!!!!!!
short line
loooooooooooooooooooooooooooong
short
```

```haskell
$ ghc --make shortlinesonly
[1 of 1] Compiling Main             ( shortlinesonly.hs, shortlinesonly.o )
Linking shortlinesonly ...
$ cat shortlines.txt | ./shortlinesonly
i'm short
so am i
short
```

我們把 shortlines.txt 的內容經由 pipe 送給 shortlinesonly，結果就如你看到，我們只有得到比較短的行。

從輸入那一些字串，經由一些轉換然後輸出這樣的模式實在太常用了。常用到甚至建立了一個函數叫 **interact**。`interact` 接受一個 `String -> String` 的函數，並回傳一個 I/O action。那個 I/O action 會讀取一些輸入，呼叫提供的函數，然後把函數的結果打印出來。所以我們的程式可以改寫成這樣。

```haskell
main = interact shortLinesOnly

shortLinesOnly :: String -> String
shortLinesOnly input =
    let allLines = lines input
        shortLines = filter (\line -> length line < 10) allLines
        result = unlines shortLines
    in result
```

我們甚至可以再讓程式碼更短一些，像這樣

```haskell
main = interact $ unlines . filter ((<10) . length) . lines
```

看吧，我們讓程式縮到只剩一行了，很酷吧！

能應用 `interact` 的情況有幾種，像是從輸入 pipe 讀進一些內容，然後丟出一些結果的程式；或是從使用者獲取一行一行的輸入，然後丟回根據那一行運算的結果，再拿取另一行。這兩者的差別主要是取決於使用者使用他們的方式。

我們再來寫另一個程式，它不斷地讀取一行行並告訴我們那一行字串是不是一個回文字串 (palindrome)。我們當然可以用 `getLine` 讀取一行然後再呼叫 `main` 作同樣的事。不過同樣的事情可以用 `interact` 更簡潔地達成。當使用 `interact` 的時候，想像你是將輸入經有某些轉換成輸出。在這個情況當中，我們要將每一行輸入轉換成 `"palindrome"` 或 `"not a palindrome"`。所以我們必須寫一個函數將 `"elephant\nABCBA\nwhatever"` 轉換成 `not a palindrome\npalindrome\nnot a palindrome"`。來動手吧！

```haskell
respondPalindromes contents = unlines (map (\xs ->
    if isPalindrome xs then "palindrome" else "not a palindrome") (lines contents))
        where isPalindrome xs = xs == reverse xs
```

再來將程式改寫成 point-free 的形式

```haskell
respondPalindromes = unlines . map (\xs ->
    if isPalindrome xs then "palindrome" else "not a palindrome") . lines
        where isPalindrome xs = xs == reverse xs
```

很直覺吧！首先將 `"elephant\nABCBA\nwhatever"` 變成 `["elephant", "ABCBA", "whatever"]` 然後將一個 lambda 函數 map 它，`["not a palindrome", "palindrome", "not a palindrome"]` 然後用 `unlines` 變成一行字串。接著

```haskell
main = interact respondPalindromes
```

來測試一下吧。

```haskell
$ runhaskell palindrome.hs
hehe
not a palindrome
ABCBA
palindrome
cookie
not a palindrome
```

即使我們的程式是把一大把字串轉換成另一個，其實他表現得好像我們是一行一行做的。這是因為 Haskell 是惰性的，程式想要打印出第一行結果時，他必須要先有第一行輸入。所以一旦我們給了第一行輸入，他便打印出第一行結果。我們用 end-of-line 字元來結束程式。

我們也可以用 pipe 的方式將輸入餵給程式。假設我們有這樣一個檔案。

```haskell
dogaroo
radar
rotor
madam
```

將他存為 `words.txt`，將他餵給程式後得到的結果

```haskell
$ cat words.txt | runhaskell palindromes.hs
not a palindrome
palindrome
palindrome
palindrome
```

再一次地提醒，我們得到的結果跟我們自己一個一個字打進輸入的內容是一樣的。我們看不到 `palindrome.hs` 輸入的內容是因為內容來自於檔案。

你應該大致了解 Lazy I/O 是如何運作，並能善用他的優點。他可以從輸入轉換成輸出的角度方向思考。由於 Lazy I/O，沒有輸入在被用到之前是真的被讀入。

到目前為止，我們的示範都是從終端讀取某些東西或是打印出某些東西到終端。但如果我們想要讀寫檔案呢？其實從某個角度來說我們已經作過這件事了。我們可以把讀寫終端想成讀寫檔案。只是把檔案命名成 `stdout` 跟 `stdin` 而已。他們分別代表標準輸出跟標準輸入。我們即將看到的讀寫檔案跟讀寫終端並沒什麼不同。

首先來寫一個程式，他會開啟一個叫 girlfriend.txt 的檔案，檔案裡面有 Avril Lavigne 的暢銷名曲 Girlfriend，並將內容打印到終端上。接下來是 girlfriend.txt 的內容。

```haskell
Hey! Hey! You! You!
I don't like your girlfriend!
No way! No way!
I think you need a new one!
```

這則是我們的主程式。

```haskell
import System.IO

main = do
    handle <- openFile "girlfriend.txt" ReadMode
    contents <- hGetContents handle
    putStr contents
    hClose handle
```

執行他後得到的結果。

```haskell
$ runhaskell girlfriend.hs
Hey! Hey! You! You!
I don't like your girlfriend!
No way! No way!
I think you need a new one!
```

我們來一行行看一下程式。我們的程式用 do 把好幾個 I/O action 綁在一起。在 do block 的第一行，我們注意到有一個新的函數叫 **openFile**。他的 type signature 是 `openFile :: FilePath -> IOMode -> IO Handle`。他說了 `openFile` 接受一個檔案路徑跟一個 `IOMode`，並回傳一個 I/O action，他會打開一個檔案並把檔案關聯到一個 handle。

`FilePath` 不過是 `String` 的 type synonym。

```haskell
type FilePath = String
```

`IOMode` 則是一個定義如下的型態

```haskell
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
```

![](/files/-LjUmt7BshmjkYCXzMll)

就像我們之前定義的型態，分別代表一個星期的七天。這個型態代表了我們想對打開的檔案做什麼。很簡單吧。留意到我們的型態是 `IOMode` 而不是 `IO Mode`。`IO Mode` 代表的是一個 I/O action 包含了一個型態為 `Mode` 的值，但 `IOMode` 不過是一個陽春的 enumeration。

最後，他回傳一個 I/O action 會將指定的檔案用指定的模式打開。如果我們將 I/O action 綁定到某個東西，我們會得到一個 `Handle`。型態為 `Handle` 的值代表我們的檔案在哪裡。有了 handle 我們才知道要從哪個檔案讀取內容。想讀取檔案但不將檔案綁定到 handle 上這樣做是很蠢的。所以，我們將一個 handle 綁定到 `handle`。

接著一行，我們看到一個叫 **hGetContents** 的函數。他接了一個 `Handle`，所以他知道要從哪個檔案讀取內容並回傳一個 `IO String`。一個包含了檔案內容的 I/O action。這函數跟 `getContents` 差不多。唯一的差別是 `getContents` 會自動從標準輸入讀取內容（也就是終端），而 `hGetContents` 接了一個 file handle，這 file handle 告訴他讀取哪個檔案。除此之外，他們都是一樣的。就像 `getContents`，`hGetContents` 不會把檔案一次都拉到記憶體中，而是有必要才會讀取。這非常酷，因為我們把 `contents` 當作是整個檔案般用，但他實際上不在記憶體中。就算這是個很大的檔案，`hGetContents` 也不會塞爆你的記憶體，而是只有必要的時候才會讀取。

要留意檔案的 handle 還有檔案的內容兩個概念的差異，在我們的程式中他們分別被綁定到 `handle` 跟 `contents` 兩個名字。handle 是我們拿來區分檔案的依據。如果你把整個檔案系統想成一本厚厚的書，每個檔案分別是其中的一個章節，handle 就像是書籤一般標記了你現在正在閱讀（或寫入）哪一個章節，而內容則是章節本身。

我們使用 `putStr contents` 打印出內容到標準輸出，然後我們用了 **hClose**。他接受一個 handle 然後回傳一個關掉檔案的 I/O action。在用了 `openFile` 之後，你必須自己把檔案關掉。

要達到我們目的的另一種方式是使用 **withFile**，他的 type signature 是 `withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a`。他接受一個檔案路徑，一個 `IOMode` 以及一個函數，這函數則接受一個 handle 跟一個 I/O action。`withFile` 最後回傳一個會打開檔案，對檔案作某件事然後關掉檔案的 I/O action。處理的結果是包在最後的 I/O action 中，這結果跟我們給的函數的回傳是相同的。這聽起來有些複雜，但其實很簡單，特別是我們有 lambda，來看看我們用 `withFile` 改寫前面程式的一個範例：

```haskell
import System.IO

main = do
    withFile "girlfriend.txt" ReadMode (\handle -> do
            contents <- hGetContents handle
            putStr contents)
```

正如你看到的，程式跟之前的看起來很像。`(\handle -> ... )` 是一個接受 handle 並回傳 I/O action 的函數，他通常都是用 lambda 來表示。我們需要一個回傳 I/O action 的函數的理由而不是一個本身作處理並關掉檔案的 I/O action，是因為這樣一來那個 I/O action 不會知道他是對哪個檔案在做處理。用 `withFile` 的話，`withFile` 會打開檔案並把 handle 傳給我們給他的函數，之後他則拿到一個 I/O action，然後作成一個我們描述的 I/O action，最後關上檔案。例如我們可以這樣自己作一個 `withFile`：

```haskell
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
    handle <- openFile path mode
    result <- f handle
    hClose handle
    return result
```

![](/files/-LjUmt7FWKV0MJPFBzs0)

我們知道要回傳的是一個 I/O action，所以我們先放一個 do。首先我們打開檔案，得到一個 handle。然後我們 apply `handle` 到我們的函數，並得到一個做事的 I/O action。我們綁定那個 I/O action 到 `result` 這個名字，關上 handle 並 `return result`。`return` 的作用把從 `f` 得到的結果包在 I/O action 中，這樣一來 I/O action 中就包含了 `f handle` 得到的結果。如果 `f handle` 回傳一個從標準輸入讀去數行並寫到檔案然後回傳讀入的行數的 I/O action，在 `withFile'` 的情形中，最後的 I/O action 就會包含讀入的行數。

就像 `hGetContents` 對應 `getContents` 一樣，只不過是針對某個檔案。我們也有 **hGetLine**、**hPutStr**、**hPutStrLn**、**hGetChar** 等等。他們分別是少了 h 的那些函數的對應。只不過他們要多拿一個 handle 當參數，並且是針對特定檔案而不是標準輸出或標準輸入。像是 `putStrLn` 是一個接受一個字串並回傳一個打印出加了換行字元的字串的 I/O action 的函數。`hPutStrLn` 接受一個 handle 跟一個字串，回傳一個打印出加了換行字元的字串到檔案的 I/O action。以此類推，`hGetLine` 接受一個 handle 然後回傳一個從檔案讀取一行的 I/O action。

讀取檔案並對他們的字串內容作些處理實在太常見了，常見到我們有三個函數來更進一步簡化我們的工作。

**readFile** 的 type signature 是 `readFile :: FilePath -> IO String`。記住，`FilePath` 不過是 `String` 的一個別名。`readFile` 接受一個檔案路徑，回傳一個惰性讀取我們檔案的 I/O action。然後將檔案的內容綁定到某個字串。他比起先 `openFile`，綁定 handle，然後 `hGetContents` 要好用多了。這邊是一個用 `readFile` 改寫之前例子的範例：

```haskell
import System.IO

main = do
    contents <- readFile "girlfriend.txt"
    putStr contents
```

由於我們拿不到 handle，所以我們也無法關掉他。這件事 Haskell 的 `readFile` 在背後幫我們做了。

**writeFile** 的型態是 `writefile :: FilePath -> String -> IO ()`。他接受一個檔案路徑，以及一個要寫到檔案中的字串，並回傳一個寫入動作的 I/O action。如果這個檔案已經存在了，他會先把檔案內容都砍了再寫入。下面示範了如何把 girlfriend.txt 的內容轉成大寫然後寫入到 girlfriendcaps.txt 中

```haskell
import System.IO
import Data.Char

main = do
    contents <- readFile "girlfriend.txt"
    writeFile "girlfriendcaps.txt" (map toUpper contents)
```

```haskell
$ runhaskell girlfriendtocaps.hs
$ cat girlfriendcaps.txt
HEY! HEY! YOU! YOU!
I DON'T LIKE YOUR GIRLFRIEND!
NO WAY! NO WAY!
I THINK YOU NEED A NEW ONE!
```

**appendFile** 的型態很像 `writeFile`，只是 `appendFile` 並不會在檔案存在時把檔案內容砍掉而是接在後面。

假設我們有一個檔案叫 todo.txt\`\`，裡面每一行是一件要做的事情。現在我們寫一個程式，從標準輸入接受一行將他加到我們的 to-do list 中。

```haskell
import System.IO

main = do
    todoItem <- getLine
    appendFile "todo.txt" (todoItem ++ "\n")
```

```haskell
$ runhaskell appendtodo.hs
Iron the dishes
$ runhaskell appendtodo.hs
Dust the dog
$ runhaskell appendtodo.hs
Take salad out of the oven
$ cat todo.txt
Iron the dishes
Dust the dog
Take salad out of the oven
```

由於 `getLine` 回傳的值不會有換行字元，我們需要在每一行最後加上 `"\n"`。

還有一件事，我們提到 `contents <- hGetContents handle` 是惰性 I/O，不會將檔案一次都讀到記憶體中。 所以像這樣寫的話：

```haskell
main = do
    withFile "something.txt" ReadMode (\handle -> do
        contents <- hGetContents handle
        putStr contents)
```

實際上像是用一個 pipe 把檔案弄到標準輸出。正如你可以把 list 想成 stream 一樣，你也可以把檔案想成 stream。他會每次讀一行然後打印到終端上。你也許會問這個 pipe 究竟一次可以塞多少東西，讀去硬碟的頻率究竟是多少？對於文字檔而言，預設的 buffer 通常是 line-buffering。這代表一次被讀進來的大小是一行。這也是為什麼在這個 case 我們是一行一行處理。對於 binary file 而言，預設的 buffer 是 block-buffering。這代表我們是一個 chunk 一個 chunk 去讀得。而一個 chunk 的大小是根據作業系統不同而不同。

你能用 `hSetBuffering` 來控制 buffer 的行為。他接受一個 handle 跟一個 `BufferMode`，回傳一個會設定 buffer 行為的 I/O action。`BufferMode` 是一個 enumeration 型態，他可能的值有：`NoBuffering`, `LineBuffering` 或 `BlockBuffering (Maybe Int)`。其中 `Maybe Int` 是表示一個 chunck 有幾個 byte。如果他的值是 `Nothing`，則作業系統會幫你決定 chunk 的大小。`NoBuffering` 代表我們一次讀一個 character。一般來說 `NoBuffering` 的表現很差，因為他存取硬碟的頻率很高。

接下來是我們把之前的範例改寫成用 2048 bytes 的 chunk 讀取，而不是一行一行讀。

```haskell
main = do
    withFile "something.txt" ReadMode (\handle -> do
        hSetBuffering handle $ BlockBuffering (Just 2048)
        contents <- hGetContents handle
        putStr contents)
```

用更大的 chunk 來讀取對於減少存取硬碟的次數是有幫助的，特別是我們的檔案其實是透過網路來存取。

我們也可以使用 **hFlush**，他接受一個 handle 並回傳一個會 flush buffer 到檔案的 I/O action。當我們使用 line-buffering 的時候，buffer 在每一行都會被 flush 到檔案。當我們使用 block-buffering 的時候，是在我們讀每一個 chunk 作 flush 的動作。flush 也會發生在關閉 handle 的時候。這代表當我們碰到換行字元的時候，讀或寫的動作都會停止並回報手邊的資料。但我們能使用 `hFlush` 來強迫回報所有已經在 buffer 中的資料。經過 flushing 之後，資料也就能被其他程式看見。

把 block-buffering 的讀取想成這樣：你的馬桶會在水箱有一加侖的水的時候自動沖水。所以你不斷灌水進去直到一加侖，馬桶就會自動沖水，在水裡面的資料也就會被看到。但你也可以手動地按下沖水鈕來沖水。他會讓現有的水被沖走。沖水這個動作就是 `hFlush` 這個名字的含意。

我們已經寫了一個將 item 加進 to-do list 裡面的程式，現在我們想加進移除 item 的功能。我先把程式碼貼上然後講解他。我們會使用一些新面孔像是 `System.Directory` 以及 `System.IO` 裡面的函數。

來看一下我們包含移除功能的程式:

```haskell
import System.IO
import System.Directory
import Data.List

main = do
    handle <- openFile "todo.txt" ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let todoTasks = lines contents
    numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStrLn "These are your TO-DO items:"
    putStr $ unlines numberedTasks
    putStrLn "Which one do you want to delete?"
    numberString <- getLine
    let number = read numberString
    newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile "todo.txt"
    renameFile tempName "todo.txt"
```

一開始，我們用 read mode 打開 todo.txt，並把他綁定到 `handle`。

接著，我們使用了一個之前沒用過在 `System.IO` 中的函數 **openTempFile**。他的名字淺顯易懂。他接受一個暫存的資料夾跟一個樣板檔案名，然後打開一個暫存檔。我們使用 `"."` 當作我們的暫存資料夾，因為 `.` 在幾乎任何作業系統中都代表了現在所在的資料夾。我們使用 `"temp"` 當作我們暫存檔的樣板名，他代表暫存檔的名字會是 temp 接上某串隨機字串。他回傳一個創建暫存檔的 I/O action，然後那個 I/O action 的結果是一個 pair：暫存檔的名字跟一個 handle。我們當然可以隨便開啟一個 todo2.txt 這種名字的檔案。但使用 `openTempFile` 會是比較好的作法，這樣你不會不小心覆寫任何檔案。

我們不用 `getCurrentDirectory` 的來拿到現在所在資料夾而用 `"."` 的原因是 `.` 在 unix-like 系統跟 Windows 中都表示現在的資料夾。

然後，我們綁定 todo.txt 的內容成 `contents`。把字串斷成一串字串，每個字串代表一行。`todoTasks` 就變成 `["Iron the dishes", "Dust the dog", "Take salad out of the oven"]`。我們用一個會把 3 跟 `"hey"` 變成 `"3 - hey"` 的函數，然後從 0 開始把這個串列 zip 起來。所以 `numberedTasks` 就是 `["0 - Iron the dishes", "1 - Dust the dog" ...`。我們用 `unlines` 把這個串列變成一行，然後打印到終端上。注意我們也有另一種作法，就是用 `mapM putStrLn numberedTasks`。

我們問使用者他們想要刪除哪一個並且等著他們輸入一個數字。假設他們想要刪除 1 號，那代表 `Dust the dog`，所以他們輸入 `1`。於是 `numberString` 就代表 `"1"`。由於我們想要一個數字，而不是一個字串，所以我們用對 `1` 使用 `read`，並且綁定到 `number`。

還記得在 `Data.List` 中的 `delete` 跟 `!!` 嗎？`!!` 回傳某個 index 的元素，而 `delete` 刪除在串列中第一個發現的元素，然後回傳一個新的沒有那個元素的串列。`(todoTasks !! number)` （number 代表 `1`） 回傳 `"Dust the dog"`。我們把 `todoTasks` 去掉第一個 `"Dust the dog"` 後的串列綁定到 `newTodoItems`，然後用 `unlines` 變成一行然後寫到我們所打開的暫存檔。舊有的檔案並沒有變動，而暫存檔包含砍掉那一行後的所有內容。

在我們關掉原始檔跟暫存檔之後我們用 **removeFile** 來移除原本的檔案。他接受一個檔案路徑並且刪除檔案。刪除舊得 todo.txt 之後，我們用 **renameFile** 來將暫存檔重新命名成 todo.txt。特別留意 `removeFile` 跟 `renameFile`（兩個都在 `System.Directory` 中）接受的是檔案路徑，而不是 handle。

這就是我們要的，實際上我們可以用更少行寫出同樣的程式，但我們很小心地避免覆寫任何檔案，並詢問作業系統我們可以把暫存檔擺在哪？讓我們來執行看看。

```haskell
$ runhaskell deletetodo.hs
These are your TO-DO items:
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
Which one do you want to delete?
1

$ cat todo.txt
Iron the dishes
Take salad out of the oven

$ runhaskell deletetodo.hs
These are your TO-DO items:
0 - Iron the dishes
1 - Take salad out of the oven
Which one do you want to delete?
0

$ cat todo.txt
Take salad out of the oven
```

## 命令列引數

![](/files/-LjUmt7NAwAY5RXV0mot)

如果你想要寫一個在終端裡運行的程式，處理命令列引數是不可或缺的。幸運的是，利用 Haskell 的 Standard Libary 能讓我們有效地處理命令列引數。

在之前的章節中，我們寫了一個能將 to-do item 加進或移除 to-do list 的一個程式。但我們的寫法有兩個問題。第一個是我們把放 to-do list 的檔案名稱給寫死了。我們擅自決定使用者不會有很多個 to-do lists，就把檔案命名為 todo.txt。

一種解決的方法是每次都詢問使用者他們想將他們的 to-do list 放進哪個檔案。我們在使用者要刪除的時候也採用這種方式。這是一種可以運作的方式，但不太能被接受，因為他需要使用者運行程式，等待程式詢問才能回答。這被稱為互動式的程式，但討厭的地方在當你想要自動化執行程式的時候，好比說寫成 script，這會讓你的 script 寫起來比較困難。

這也是為什麼有時候讓使用者在執行的時候就告訴程式他們要什麼會比較好，而不是讓程式去問使用者要什麼。比較好的方式是讓使用者透過命令列引數告訴程式他們想要什麼。

在 `System.Environment` 模組當中有兩個很酷的 I/O actions，一個是 **getArgs**，他的 type 是 `getArgs :: IO [String]`，他是一個拿取命令列引數的 I/O action，並把結果放在包含的一個串列中。**getProgName** 的型態是 `getProgName :: IO String`，他則是一個 I/O action 包含了程式的名稱。

我們來看一個展現他們功能的程式。

```haskell
import System.Environment
import Data.List

main = do
    args <- getArgs
    progName <- getProgName
    putStrLn "The arguments are:"
    mapM putStrLn args
    putStrLn "The program name is:"
    putStrLn progName
```

我們將 `getArgs` 跟 `progName` 分別綁定到 `args` 跟 `progName`。我們打印出 `The arguments are:` 以及在 `args` 中的每個引數。最後，我們打印出程式的名字。我們把程式編譯成 `arg-test`。

```haskell
$ ./arg-test first second w00t "multi word arg"
The arguments are:
first
second
w00t
multi word arg
The program name is:
arg-test
```

知道了這些函數現在你能寫幾個很酷的命令列程式。在之前的章節，我們寫了一個程式來加入待作事項，也寫了另一個程式刪除事項。現在我們要把兩個程式合起來，他會根據命令列引數來決定該做的事情。我們也會讓程式可以處理不同的檔案，而不是只有 todo.txt

我們叫這程式 todo，他會作三件事：

```
# 檢視待作事項
# 加入待作事項
# 刪除待作事項
```

我們暫不考慮不合法的輸入這件事。

我們的程式要像這樣運作：假如我們要加入 `Find the magic sword of power`，則我們會打 `todo add todo.txt "Find the magic sword of power"`。要檢視事項我們則會打 `todo view todo.txt`，如果要移除事項二則會打 `todo remove todo.txt 2`

我們先作一個分發的 association list。他會把命令列引數當作 key，而對應的處理函數當作 value。這些函數的型態都是 `[String] -> IO ()`。他們會接受命令列引數的串列並回傳對應的檢視，加入以及刪除的 I/O action。

```haskell
import System.Environment
import System.Directory
import System.IO
import Data.List

dispatch :: [(String, [String] -> IO ())]
dispatch =  [ ("add", add)
            , ("view", view)
            , ("remove", remove)
            ]
```

我們定義了 `main`，`add`，`view` 跟 `remove`，就從 `main` 開始講吧：

```haskell
main = do
    (command:args) <- getArgs
    let (Just action) = lookup command dispatch
    action args
```

首先，我們取出引數並把他們綁定到 `(command:args)`。如果你還記得 pattern matching，這麼做會把第一個引數綁定到 `command`，把其他的綁定到 `args`。如果我們像這樣執行程式 `todo add todo.txt "Spank the monkey"`，`command` 會變成 `"add"`，而 `args` 會變成 `["todo.txt", "Spank the monkey"]`。

在下一行，我們在一個分派的串列中尋到我們的指令是哪個。由於 `"add"` 指向 `add`，我們的結果便是 `Just add`。我們再度使用了 pattern matching 來把我們的函數從 `Maybe` 中取出。但如果我們想要的指令不在分派的串列中呢？那樣 lookup 就會回傳 `Nothing`，但我們這邊並不特別處理失敗的情況，所以 pattern matching 會失敗然後我們的程式就會當掉。

最後，我們用剩下的引數呼叫 `action` 這個函數。他會還傳一個加入 item，顯示所有 items 或者刪除 item 的 I/O action。由於這個 I/O action 是在 `main` 的 do block 中，他最後會被執行。如果我們的 `action` 函數是 `add`，他就會被餵 `args` 然後回傳一個加入 `Spank the monkey` 到 todo.txt 中的 I/O action。

我們剩下要做的就是實作 `add`，`view` 跟 `remove`，我們從 `add` 開始：

```haskell
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
```

如果我們這樣執行程式 `todo add todo.txt "Spank the monkey"`，則 `"add"` 會被綁定到 `command`，而 `["todo.txt", "Spank the monkey"]` 會被帶到從 dispatch list 中拿到的函數。

由於我們不處理不合法的輸入，我們只針對這兩項作 pattern matching，然後回傳一個附加一行到檔案末尾的 I/O action。

接著，我們來實作檢視串列。如果我們想要檢視所有 items，我們會 `todo view todo.txt`。所以 `command` 會是 `"view"`，而 `args` 會是 `["todo.txt"]`。

```haskell
view :: [String] -> IO ()
view [fileName] = do
    contents <- readFile fileName
    let todoTasks = lines contents
    numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStr $ unlines numberedTasks
```

這跟我們之前刪除檔案的程式差不多，只是我們是在顯示內容而已，

最後，我們要來實作 `remove`。他基本上跟之前寫的只有刪除功能的程式很像，所以如果你不知道刪除是怎麼做的，可以去看之前的解釋。主要的差別是我們不寫死 todo.txt，而是從參數取得。我們也不會提示使用者要刪除哪一號的 item，而是從參數取得。

```haskell
remove :: [String] -> IO ()
remove [fileName, numberString] = do
    handle <- openFile fileName ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let number = read numberString
        todoTasks = lines contents
        newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile fileName
    renameFile tempName fileName
```

我們打開 `fileName` 的檔案以及一個暫存。刪除使用者要我們刪的那一行後，把檔案內容寫到暫存檔。砍掉原本的檔案然後把暫存檔重新命名成 `fileName`。

來看看完整的程式。

```haskell
import System.Environment
import System.Directory
import System.IO
import Data.List

dispatch :: [(String, [String] -> IO ())]
dispatch =  [ ("add", add)
            , ("view", view)
            , ("remove", remove)
            ]

main = do
    (command:args) <- getArgs
    let (Just action) = lookup command dispatch
    action args

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")

view :: [String] -> IO ()
view [fileName] = do
    contents <- readFile fileName
    let todoTasks = lines contents
        numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStr $ unlines numberedTasks

remove :: [String] -> IO ()
remove [fileName, numberString] = do
    handle <- openFile fileName ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let number = read numberString
        todoTasks = lines contents
        newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile fileName
    renameFile tempName fileName
```

![](/files/-LjUmt7UMs8yB-RYWPgP)

總結我們的程式：我們做了一個 dispatch association，將指令對應到一些會接受命令列引數並回傳 I/O action 的函數。我們知道使用者下了什麼命令，並根據那個命令從 dispatch list 取出對影的函數。我們用剩下的命令列引數呼叫哪些函數而得到一些作相對應事情的 I/O action。然後便執行那些 I/O action。

在其他程式語言，我們可能會用一個大的 switch case 來實作，但使用高階函數讓我們可以要 dispatch list 給我們要的函數，並要那些函數給我們適當的 I/O action。

讓我們看看執行結果。

```haskell
$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven

$ ./todo add todo.txt "Pick up children from drycleaners"

$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
3 - Pick up children from drycleaners

$ ./todo remove todo.txt 2

$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Pick up children from drycleaners
```

要再另外加新的選項也是很容易。只要在 dispatch list 加入新的會作你要的事情函數。你可以試試實作一個 `bump` 函數，接受一個檔案跟一個 task number，他會回傳一個把那個 task 搬到 to-do list 頂端的 I/O action。

對於不合法的輸入你也可以讓程式結束地漂亮一點。(例如使用者輸入了 `todo UP YOURS HAHAHAHA`)可以作一個回報錯誤的 I/O action (例如 \`\`errorExist :: IO ())檢查有沒有不合法的輸入，如果有便執行這個回報錯誤的 I/O action。我們之後會談另一個可能，就是用 exception。

## 亂數

![](/files/-LjUmt7WsVDw1Kd8ehQQ)

在許多情況下，你寫程式會需要些隨機的資料。或許你在製作一個遊戲，在遊戲中你需要擲骰子。或是你需要測試程式的測試資料。精準一點地說，我們需要 pseudo-random 的資料，我們知道真正的隨機資料好比是一隻猴子拿著起司跟奶油騎在單輪車上，任何事情都會發生。在這個章節，我們要看看如何讓 Haskell 產生些 pseudo-random 的資料。

在大多數其他的程式語言中，會給你一些函數能讓你拿到些隨機亂數。每呼叫一次他就會拿到一個不同的數字。那在 Haskell 中是如何？要記住 Haskell 是一個純粹函數式語言。代表任何東西都具有 referential transparency。那代表你餵給一個函數相同的參數，不管怎麼呼叫都是回傳相同的結果。這很新奇的原因是因為他讓我們理解程式的方式不同，而且可以讓我們延遲計算，直到我們真正需要他。如果我呼叫一個函數，我可以確定他不會亂來。我真正在乎的是他的結果。然而，這會造成在亂數的情況有點複雜。如果我有一個函數像這樣：

```haskell
randomNumber :: (Num a) => a
randomNumber = 4
```

由於他永遠回傳 `4`，所以對於亂數的情形而言是沒什麼意義。就算 4 這個結果是擲骰子來的也沒有意義。

其他的程式語言是怎麼產生亂數的呢？他們可能隨便拿取一些電腦的資訊，像是現在的時間，你怎麼移動你的滑鼠，以及周圍的聲音。根據這些算出一個數值讓他看起來好像隨機的。那些要素算出來的結果可能在每個時間都不同，所以你會拿到不同的隨機數字。

所以說在 Haskell 中，假如我們能作一個函數，他會接受一個具隨機性的參數，然後根據那些資訊還傳一個數值。

在 `System.Random` 模組中。他包含所有滿足我們需求的函數。讓我們先來看其中一個，就是 **random**。他的型態是 `random :: (RandomGen g, Random a) => g -> (a, g)`。哇，出現了新的 typeclass。**RandomGen** typeclass 是指那些可以當作亂源的型態。而**Random** typeclass 則是可以裝亂數的型態。一個布林值可以是隨機值，不是 `True` 就是 `False`。一個整數可以是隨機的好多不同值。那你會問，函數可以是一個隨機值嗎？我不這麼認為。如果我們試著翻譯 `random` 的型態宣告，大概會是這樣：他接受一個 random generator (亂源所在)，然後回傳一個隨機值以及一個新的 random generator。為什麼他要回傳一個新的 random generator 呢？就是下面我們要講的。

要使用 `random` 函數， 我們必須要了解 random generator。 在 `System.Random` 中有一個很酷的型態，叫做 **StdGen**， 他是 `RandomGen` 的一個 instance。 我們可以自己手動作一個 `StdGen` 也可以告訴系統給我們一個現成的。

要自己做一個 random generator，要使用 **mkStdGen** 這個函數。他的型態是 `mkStdGen :: Int -> StdGen`。他接受一個整數，然後根據這個整數會給一個 random generator。讓我們來試一下 `random` 以及 `mkStdGen`，用他們產生一個亂數吧。

```haskell
ghci> random (mkStdGen 100)
```

```haskell
<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
        `Random a' arising from a use of `random' at <interactive>:1:0-20
    Probable fix: add a type signature that fixes these type variable(s)  `
```

這是什麼？由於 `random` 函數會回傳 `Random` typeclass 中任何一種型態，所以我們必須告訴 Haskell 我們是要哪一種型態。不要忘了我們是回傳 random value 跟 random generator 的一個 pair

```haskell
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)
```

我們終於有了一個看起來像亂數的數字。tuple 的第一個部份是我們的亂數，而第二個部份是一個新的 random generator 的文字表示。如果我們用相同的 random generator 再呼叫 `random` 一遍呢？

```haskell
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)
```

不易外地我們得到相同的結果。所以我們試試用不同的 random generator 作為我們的參數。

```haskell
ghci> random (mkStdGen 949494) :: (Int, StdGen)
(539963926,466647808 1655838864)
```

很好，我們拿到了不同的數字。我們可以用不同的型態標誌來拿到不同型態的亂數

```haskell
ghci> random (mkStdGen 949488) :: (Float, StdGen)
(0.8938442,1597344447 1655838864)
ghci> random (mkStdGen 949488) :: (Bool, StdGen)
(False,1485632275 40692)
ghci> random (mkStdGen 949488) :: (Integer, StdGen)
(1691547873,1597344447 1655838864)
```

讓我們寫一個模擬丟三次銅板的函數。假如 `random` 不同時回傳一個亂數以及一個新的 random generator，我們就必須讓這函數接受三個 random generators 讓他們每個回傳一個擲銅板的結果。但那樣聽起來怪怪的，加入一個 generator 可以產生一個型態是 `Int` 的亂數，他應該可以產生擲三次銅板的結果（總共才八個組合）。這就是 `random` 為什麼要回傳一個新的 generator 的關鍵了。

我們將一個銅板表示成 `Bool`。`True` 代表反面，`False` 代表正面。

```haskell
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen
    (secondCoin, newGen') = random newGen
    (thirdCoin, newGen') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)  )
```

我們用我們拿來當參數的 generator 呼叫 `random` 並得到一個擲銅板的結果跟一個新的 generator。然後我們再用新的 generator 呼叫他一遍，來得到第二個擲銅板的結果。對於第三個擲銅板的結果也是如法炮製。如果我們一直都用同樣的 generator，那所有的結果都會是相同的值。也就是不是 `(False, False, False)` 就是 `(True, True, True)`。

```haskell
ghci> threeCoins (mkStdGen 21)
(True,True,True)
ghci> threeCoins (mkStdGen 22)
(True,False,True)
ghci> threeCoins (mkStdGen 943)
(True,False,True)
ghci> threeCoins (mkStdGen 944)
(True,True,True)
```

留意我們不需要寫 `random gen :: (Bool, StdGen)`。那是因為我們已經在函數的型態宣告那邊就表明我們要的是布林。而 Haskell 可以推敲出我們要的是布林值。

假如我們要的是擲四次？甚至五次呢？有一個函數叫 **randoms**，他接受一個 generator 並回傳一個無窮序列。

```haskell
ghci> take 5 $ randoms (mkStdGen 11) :: [Int]
[-1807975507,545074951,-1015194702,-1622477312,-502893664]
ghci> take 5 $ randoms (mkStdGen 11) :: [Bool]
[True,True,True,True,False]
ghci> take 5 $ randoms (mkStdGen 11) :: [Float]
[7.904789e-2,0.62691015,0.26363158,0.12223756,0.38291094]
```

為什麼 `randoms` 不另外多回傳一個新的 generator 呢？我們可以這樣地實作 `randoms`

```haskell
randoms' :: (RandomGen g, Random a) => g -> [a]
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen
```

一個遞迴的定義。我們由現在的 generator 拿到一個亂數跟一個新的 generator，然後製作一個 list，list 的第一個值是那個亂數，而 list 的其餘部份是根據新的 generator 產生出的其餘亂數們。由於我們可能產生出無限的亂數，所以不可能回傳一個新的 generator。

我們可以寫一個函數，他會回傳有限個亂數跟一個新的 generator

```haskell
finiteRandoms :: (RandomGen g, Random a, Num n, Eq n) => n -> g -> ([a], g)
finiteRandoms 0 gen = ([], gen)
finiteRandoms n gen =
    let (value, newGen) = random gen
        (restOfList, finalGen) = finiteRandoms (n-1) newGen
    in  (value:restOfList, finalGen)
```

又是一個遞迴的定義。我們說如果我們要 0 個亂數，我們便回傳一個空的 list 跟原本給我們的 generator。對於其他數量的亂數，我們先拿一個亂數跟一個新的 generator。這一個亂數便是 list 的第一個數字。然後 list 中剩下的便是 n-1 個由新的 generator 產生出的亂數。然後我們回傳整個 list 跟最後一個產生完 n-1 個亂數後 generator。

如果我們要的是在某個範圍內的亂數呢？現在拿到的亂數要不是太大就是太小。如果我們想要的是骰子上的數字呢？**randomR** 能滿足我們的需求。他的型態是 `randomR :: (RandomGen g, Random a) :: (a, a) -> g -> (a, g)`，代表他有點類似 `random`。只不過他的第一個參數是一對數目，定義了最後產生亂數的上界以及下界。

```haskell
ghci> randomR (1,6) (mkStdGen 359353)
(6,1494289578 40692)
ghci> randomR (1,6) (mkStdGen 35935335)
(3,1250031057 40692)
```

另外也有一個 **randomRs** 的函數，他會產生一連串在給定範圍內的亂數：

```haskell
ghci> take 10 $ randomRs ('a','z') (mkStdGen 3) :: [Char]
"ndkxbvmomg"
```

這結果看起來像是一個安全性很好的密碼。

你會問你自己，這一單元跟 I/O 有關系嗎？到現在為止還沒出現任何跟 I/O 有關的東西。到現在為止我們都是手動地做我們的 random generator。但那樣的問題是，程式永遠都會回傳同樣的亂數。這在真實世界中的程式是不能接受的。這也是為什麼 `System.Random` 要提供 **getStdGen** 這個 I/O action，他的型態是 `IO StdGen`。當你的程式執行時，他會跟系統要一個 random generator，並存成一個 global generator。`getStdGen` 會替你拿那個 global random generator 並把他綁定到某個名稱上。

這裡有一個簡單的產生隨機字串的程式。

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStr $ take 20 (randomRs ('a','z') gen)
```

```haskell
$ runhaskell random_string.hs
pybphhzzhuepknbykxhe
$ runhaskell random_string.hs
eiqgcxykivpudlsvvjpg
$ runhaskell random_string.hs
nzdceoconysdgcyqjruo
$ runhaskell random_string.hs
bakzhnnuzrkgvesqplrx
```

要當心當我們連續兩次呼叫 `getStdGent` 的時候，實際上都會回傳同樣的 global generator。像這樣：

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStrLn $ take 20 (randomRs ('a','z') gen)
    gen2 <- getStdGen
    putStr $ take 20 (randomRs ('a','z') gen2)
```

你會打印出兩次同樣的字串。要能得到兩個不同的字串是建立一個無限的 stream，然後拿前 20 個字當作第一個字串，拿下 20 個字當作第二個字串。要這麼做，我們需要在 `Data.List` 中的 `splitAt` 函數。他會把一個 list 根據給定的 index 切成一個 tuple，tuple 的第一部份就是切斷的前半，第二個部份就是切斷的後半。

```haskell
import System.Random
import Data.List

main = do
    gen <- getStdGen
    let randomChars = randomRs ('a','z') gen
        (first20, rest) = splitAt 20 randomChars
        (second20, _) = splitAt 20 rest
    putStrLn first20
    putStr second20
```

另一種方法是用 **newStdGen** 這個 I/O action，他會把現有的 random generator 分成兩個新的 generators。然後會把其中一個指定成 global generator，並回傳另一個。

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStrLn $ take 20 (randomRs ('a','z') gen)
    gen' <- newStdGen
    putStr $ take 20 (randomRs ('a','z') gen')
```

當我們綁定 `newStdGen` 的時候我們不只是會拿到一個新的 generator，global generator 也會被重新指定。所以再呼叫一次 `getStdGen` 並綁定到某個名稱的話，我們就會拿到跟 `gen` 不一樣的 generator。

這邊有一個小程式會讓使用者猜數字：

```haskell
import System.Random
import Control.Monad(when)

main = do
    gen <- getStdGen
    askForNumber gen

askForNumber :: StdGen -> IO ()
askForNumber gen = do
    let (randNumber, newGen) = randomR (1,10) gen :: (Int, StdGen)
    putStr "Which number in the range from 1 to 10 am I thinking of? "
    numberString <- getLine
    when (not $ null numberString) $ do
        let number = read numberString
        if randNumber == number
            then putStrLn "You are correct!"
            else putStrLn $ "Sorry, it was " ++ show randNumber
            askForNumber newGen
```

![](/files/-LjUmt7ZwDq7ne_Sl_H6)

我們寫了一個 `askForNumber` 的函數，他接受一個 random generator 並回傳一個問使用者要數字並回答是否正確的 I/O action。在那個函數裡面，我們先根據從參數拿到的 generator 產生一個亂數以及一個新的 generator，分別叫他們為 `randomNumber` 跟 `newGen`。假設那個產生的數字是 `7`。則我們要求使用者猜我們握有的數字是什麼。我們用 `getLine` 來將結果綁定到 `numberString` 上。當使用者輸入 `7`，`numberString` 就會是 `"7"`。接下來，我們用 `when` 來檢查使用者輸入的是否是空字串。如果是，那一個空的 I/O action `return ()` 就會被回傳。基本上就等於是結束程式的意思。如果不是，那 I/O action 就會被執行。我們用 `read` 來把 `numberString` 轉成一個數字，所以 `number` 便會是 `7`。

```
如果使用者給我們一些 ``read`` 沒辦法讀取的輸入（像是 ``"haha"``），我們的程式便會當掉並打印出錯誤訊息。 如果你不希望你的程式當掉，就用 **reads**，當讀取失敗的時候他會回傳一個空的 list。當成功的時候他就回傳一個 tuple，第一個部份是我們想要的數字，第二個部份是讀取失敗的字串。
```

我們檢查如果輸入的數字跟我們隨機產生的數字一樣，便提示使用者恰當的訊息。然後再遞迴地呼叫 `askForNumber`，只是會拿到一個新的 generator。就像之前的 generator 一樣，他會給我們一個新的 I/O action。

`main` 的組成很簡單，就是由拿取一個 random generator 跟呼叫 `askForNumber` 組成罷了。

來看看我們的程式：

```haskell
$ runhaskell guess_the_number.hs
Which number in the range from 1 to 10 am I thinking of? 4
Sorry, it was 3
Which number in the range from 1 to 10 am I thinking of? 10
You are correct!
Which number in the range from 1 to 10 am I thinking of? 2
Sorry, it was 4
Which number in the range from 1 to 10 am I thinking of? 5
Sorry, it was 10
Which number in the range from 1 to 10 am I thinking of?
```

用另一種方式寫的話像這樣：

```haskell
import System.Random
import Control.Monad(when)

main = do
    gen <- getStdGen
    let (randNumber, _) = randomR (1,10) gen :: (Int, StdGen)
    putStr "Which number in the range from 1 to 10 am I thinking of? "
    numberString <- getLine
    when (not $ null numberString) $ do
        let number = read numberString
        if randNumber == number
            then putStrLn "You are correct!"
            else putStrLn $ "Sorry, it was " ++ show randNumber
        newStdGen
        main
```

他非常類似我們之前的版本，只是不是遞迴地呼叫，而是把所有的工作都在 `main` 裡面做掉。在告訴使用者他們猜得是否正確之後，便更新 global generator 然後再一次呼叫 `main`。兩種策略都是有效但我比較喜歡第一種方式。因為他在 `main` 裡面做的事比較少，並提供我們一個可以重複使用的函數。

## Bytestrings

![](/files/-LjUmt7lb7EDfXNtbIDk)

List 是一種有用又酷的資料結構。到目前為止，我們幾乎無處不使用他。有好幾個函數是專門處理 List 的，而 Haskell 惰性的性質又讓我們可以用 filter 跟 map 來替換其他語言中的 for loop 跟 while loop。也由於 evaluation 只會發生在需要的時候，像 infinite list 也對於 Haskell 不成問題（甚至是 infinite list of infinite list）。這也是為什麼 list 能被用來表達 stream，像是讀取標準輸入或是讀取檔案。我們可以打開檔案然後讀取內容成字串，即便實際上我們是需要的時候才會真正取讀取。

然而，用字串來處理檔案有一個缺點：就是他很慢。就像你所知道的，`String` 是一個 `[Char]` 的 type synonym。`Char` 沒有一個固定的大小，因為他可能由好幾個 byte 組成，好比說 Unicode。再加上 list 是惰性的。如果你有一個 list 像 `[1,2,3,4]`，他只會在需要的時候被 evaluate。所以整個 list 其實比較像是一個"保證"你會有一個 list。要記住 `[1,2,3,4]` 不過是 `1:2:3:4:[]` 的一個 syntactic sugar。當 list 的第一個元素被 evaluated 的時候，剩餘的部份 `2:3:4:[]` 一樣也只是一個"保證"你會有一個 list，以此類推。以此類推。以此類推。所以你可以想像成 list 是保證在你需要的時候會給你第一個元素，以及保證你會有剩下的部份當你還需要更多的時候。其實不難說服你這樣做並不是一個最有效率的作法。

這樣額外的負擔在大多數時候不會造成困擾，但當我們要讀取一個很大的檔案的時候就是個問題了。這也是為什麼 Haskell 要有 `bytestrings`。Bytestrings 有點像 list，但他每一個元素都是一個 byte (8 bits)，而且他們惰性的程度也是不同。

Bytestrings 有兩種：strict 跟 lazy。Strict bytestrings 放在 `Data.ByteString`，他們把惰性的性質完全拿掉。不會有所謂任何的「保證」，一個 strict bytestring 就代表一連串的 bytes。因此你不會有一個無限長的 strict bytestrings。如果你 evaluate 第一個 byte，你就必須 evalute 整個 bytestring。這麼做的優點是他會比較少 overhaed，因為他沒有 "Thunk"（也就是用 Haskell 術語來說的「保證」）。缺點就是他可能會快速消耗你的記憶體，因為你把他們一次都讀進了記憶體。

另一種 bytestring 是放在 `Data.ByteString.Lazy` 中。他們具有惰性，但又不像 list 那麼極端。就像我們之前說的，List 的 thunk 個數是跟 list 中有幾個元素一模一樣。這也是為什麼他們速度沒辦法滿足一些特殊需求。Lazy bytestrings 則用另一種作法，他們被存在 chunks 中（不要跟 Thunk 搞混），每一個 chunk 的大小是 64K。所以如果你 evaluate lazy bytestring 中的 byte，則前 64K 會被 evaluated。在那個 chunck 之後，就是一些「保證」會有剩餘的 chunk。lazy bytestrings 有點像裝了一堆大小為 64K 的 strict bytestrings 的 list。當你用 lazy bytestring 處理一個檔案的時候，他是一個 chunk 一個 chunk 去讀。這很棒是因為他不會讓我們一下使用大量的記憶體，而且 64K 有很高的可能性能夠裝進你 CPU 的 L2 Cache。

如果你大概看過 `Data.ByteString.Lazy` 的文件，你會看到到他有一堆函數的名稱跟 `Data.List` 中的函數名稱相同，只是出現的 type signature 是 `ByteString` 而不是 `[a]`，是 `Word8` 而不是 `a`。同樣名稱的函數基本上表現的行為跟 list 中的差不多。因為名稱是一樣的，所以必須用 qualified import 才不會在裝載進 GHCI 的時候造成衝突。

```haskell
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as S
```

`B` 中有 lazy bytestrings 跟對應的函數，而 `S` 中則有 strict 的版本。大多數時候我們是用 lazy 的版本。

**pack** 函數的 type signature 是 `pack :: [Word8] -> ByteString`。代表他接受一串型態為 `Word8` 的 bytes，並回傳一個 `ByteString`。你能想像一個 lazy 的 list，要讓他稍微不 lazy 一些，所以讓他對於 64K lazy。

那 `Word8` 型態又是怎麼一回事？。他就像 `Int`，只是他的範圍比較小，介於 0-255 之間。他代表一個 8-bit 的數字。就像 `Int` 一樣，他是屬於 `Num` 這個 typeclass。例如我們知道 `5` 是 polymorphic 的，他能夠表現成任何數值型態。其實 `Word8` 他也能表示。

```haskell
ghci> B.pack [99,97,110]
Chunk "can" Empty
ghci> B.pack [98..120]
Chunk "bcdefghijklmnopqrstuvwx" Empty
```

正如你看到的，你其實不必特別在意 `Word8`，因為型態系統會選擇正確的型態。如果你試著用比較大的數字，像是 `336`。那對於 `Word8` 他就會變成 `80`。

我們把一些數值打包成 `ByteString`，使他們可以塞進一個 chunk 裡面。`Empty` 之於 `ByteString` 就像 `[]` 之於 list 一樣。

**unpack** 是 `pack` 的相反，他把一個 bytestring 變成一個 byte list。

**fromChunks** 接受一串 strict 的 bytestrings 並把他變成一串 lazy bytestring。**toChunks** 接受一個 lazy bytestrings 並將他變成一串 strict bytestrings。

```haskell
ghci> B.fromChunks [S.pack [40,41,42], S.pack [43,44,45], S.pack [46,47,48]]
Chunk "()*" (Chunk "+,-" (Chunk "./0" Empty))
```

如果你有很多小的 strict bytestrings 而且不想先將他們 join 起來（會耗損 memory）這樣的作法是不錯的。

bytestring 版本的 `:` 叫做 **cons**。他接受一個 byte 跟一個 bytestring，並把這個 byte 放到 bytestring 的前端。他是 lazy 的操作，即使 bytestring 的第一個 chunk 不是滿的，他也會新增一個 chunk。這也是為什麼當你要插入很多 bytes 的時候最好用 strict 版本的 `cons`，也就是 **cons'**。

```haskell
ghci> B.cons 85 $ B.pack [80,81,82,84]
Chunk "U" (Chunk "PQRT" Empty)
ghci> B.cons' 85 $ B.pack [80,81,82,84]
Chunk "UPQRT" Empty
ghci> foldr B.cons B.empty [50..60]
Chunk "2" (Chunk "3" (Chunk "4" (Chunk "5" (Chunk "6" (Chunk "7" (Chunk "8" (Chunk "9" (Chunk ":" (Chunk ";" (Chunk "<"
Empty))))))))))
ghci> foldr B.cons' B.empty [50..60]
Chunk "23456789:;<" Empty
```

你可以看到 **empty** 製造了一個空的 bytestring。也注意到 `cons` 跟 `cons'` 的差異了嗎？有了 `foldr`，我們逐步地把一串數字從右邊開始，一個個放到 bytestring 的前頭。當我們用 `cons`，我們則得到一個 byte 一個 chunk 的結果，並不是我們要的。

bytestring 模組有一大票很像 `Data.List` 中的函數。包括了 `head`，`tail`，`init`，`null`，`length`，`map`，`reverse`，`foldl`，`foldr`，`concat`，`takeWhile`，`filter`，等等。

他也有表現得跟 `System.IO` 中一樣的函數，只有 `Strings` 被換成了 `ByteString` 而已。像是 `System.IO` 中的 `readFile`，他的型態是 `readFile :: FilePath -> IO String`，而 bytestring 模組中的 **readFile** 則是 `readFile :: FilePath -> IO ByteString`。小心，如果你用了 strict bytestring 來讀取一個檔案，他會把檔案內容都讀進記憶體中。而使用 lazy bytestring，他則會讀取 chunks。

讓我們來寫一個簡單的程式，他從命令列接受兩個檔案名，然後拷貝第一個檔案內容成第二個檔案。雖然 `System.Directory` 中已經有一個函數叫 `copyFile`，但我們想要實作自己的版本。

```haskell
import System.Environment
import qualified Data.ByteString.Lazy as B

main = do
    (fileName1:fileName2:_) <- getArgs
    copyFile fileName1 fileName2

copyFile :: FilePath -> FilePath -> IO ()
copyFile source dest = do
    contents <- B.readFile source
    B.writeFile dest contents
```

我們寫了自己的函數，他接受兩個 `FilePath`（記住 `FilePath` 不過是 `String` 的同義詞。）並回傳一個 I/O action，他會用 bytestring 拷貝第一個檔案至另一個。在 `main` 函數中，我們做的只是拿到命令列引數然後呼叫那個函數來拿到一個 I/O action。

```haskell
$ runhaskell bytestringcopy.hs something.txt ../../something.txt
```

就算我們不用 bytestring 來寫，程式最後也會長得像這樣。差別在於我們會用 `B.readFile` 跟 `B.writeFile` 而不是 `readFile` 跟 `writeFile`。有很大的可能性，就是你只要 import 檔案並在函數前加上 qualified 模組名，就可以把一個用正常 String 的程式改成用 ByteString。也有可能你是要反過來做，但那也不難。

當你需要更好的效能來讀取許多資料，嘗試用 bytestring，有很大的機會你會用很小的力氣改進很多效能。我通常用正常 String 來寫程式，然後在效能不好的時候把他們改成 ByteString。

## Exceptions (例外)

![](/files/-LjUmt7oKPdQRQwjGeiC)

所有的程式語言都有要處理失敗的情形。這就是人生。不同的語言有不同的處理方式。在 C 裡面，我們通常用非正常範圍的回傳值（像是 `-1` 或 null）來回傳錯誤。Java 跟 C#則傾向於使用 exception 來處理失敗的情況。當一個 exception 被丟出的時候，控制流程就會跳到我們做一些清理動作的地方，做完清理後 exception 被重新丟出，這樣一些處理錯誤的程式碼可以完成他們的工作。

Haskell 有一個很棒的型態系統。Algebraic data types 允許像是 `Maybe` 或 `Either` 這種型態，我們能用這些型態來代表一些可能有或沒有的結果。在 C 裡面，在失敗的時候回傳 `-1` 是很常見的事。但他只對寫程式的人有意義。如果我們不小心，我們有可能把這些錯誤碼當作正常值來處理，便造成一些混亂。Haskell 的型態系統賦予我們更安全的環境。一個 `a -> Maybe b` 的函數指出了他會產生一個包含 `b` 的 `Just`，或是回傳 `Nothing`。這型態跟 `a -> b` 是不同的，如果我們試著將兩個函數混用，compiler 便會警告我們。

儘管有表達力夠強的型態來輔助失敗的情形，Haskell 仍然支持 exception，因為 exception 在 I/O 的 contexts 下是比較合理的。在處理 I/O 的時候會有一堆奇奇怪怪的事情發生，環境是很不能被信賴的。像是打開檔案。檔案有可能被 lock 起來，也有可能檔案被移除了，或是整個硬碟都被拔掉。所以直接跳到處理錯誤的程式碼是很合理的。

我們了解到 I/O code 會丟出 exception 是件合理的事。至於 pure code 呢？其實他也能丟出 Exception。想想看 `div` 跟 `head` 兩個案例。他們的型態是 `(Integral a) => a -> a -> a` 以及 `[a] -> a`。`Maybe` 跟 `Either` 都沒有在他們的回傳型態中，但他們都有可能失敗。`div` 有可能除以零，而 `head` 有可能你傳給他一個空的 list。

```haskell
ghci> 4 `div` 0
*** Exception: divide by zero
ghci> head []
*** Exception: Prelude.head: empty list
```

![](/files/-LjUmt7qoOQMVXWlAbQ0)

pure code 能丟出 Exception，但 Exception 只能在 I/O section 中被接到（也就是在 `main` 的 do block 中）這是因為在 pure code 中你不知道什麼東西什麼時候會被 evaluate。因為 lazy 特性的緣故，程式沒有一個特定的執行順序，但 I/O code 有。

先前我們談過為什麼在 I/O 部份的程式要越少越好。程式的邏輯部份盡量都放在 pure 的部份，因為 pure 的特性就是他們的結果只會根據函數的參數不同而改變。當思考 pure function 的時候，你只需要考慮他回傳什麼，因為除此之外他不會有任何副作用。這會讓事情簡單許多。儘管 I/O 的部份是難以避免的（像是打開檔案之類），但最好是把 I/O 部份降到最低。Pure functions 預設是 lazy，那代表我們不知道他什麼時候會被 evaluate，不過我們也不該知道。然而，一旦 pure functions 需要丟出 Exception，他們何時被 evaluate 就很重要了。那是因為我們只有在 I/O 的部份才能接到 Exception。這很糟糕，因為我們說過希望 I/O 的部份越少越好。但如果我們不接 Exception，我們的程式就會當掉。這問題有解決辦法嗎？答案是不要在 pure code 裡面使用 Exception。利用 Haskell 的型態系統，盡量使用 `Either` 或 `Maybe` 之類的型態來表示可能失敗的計算。

這也是為什麼我們要來看看怎麼使用 I/O Excetion。I/O Exception 是當我們在 `main` 裡面跟外界溝通失敗而丟出的 Exception。例如我們嘗試打開一個檔案，結果發現他已經被刪掉或是其他狀況。來看看一個嘗試打開命令列引數所指定檔案名稱，並計算裡面有多少行的程式。

```haskell
import System.Environment
import System.IO

main = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
```

一個很簡單的程式。我們使用 `getArgs` I/O action，並綁定第一個 string 到 `fileName`。然後我們綁定檔案內容到 `contents`。最後，我們用 `lines` 來取得 line 的 list，並計算 list 的長度，並用 `show` 來轉換數字成 string。他如我們想像的工作，但當我們給的檔案名稱不存在的時候呢？

```haskell
$ runhaskell linecount.hs i_dont_exist.txt
linecount.hs: i_dont_exist.txt: openFile: does not exist (No such file or directory)
```

GHC 丟了錯誤訊息給我們，告訴我們檔案不存在。然後程式就掛掉了。假如我們希望打印出比較好一些的錯誤訊息呢？一種方式就是在打開檔案前檢查他存不存在。用 `System.Directory` 中的 **doesFileExist**。

```haskell
import System.Environment
import System.IO
import System.Directory

main = do (fileName:_) <- getArgs
            fileExists <- doesFileExist fileName
            if fileExists
                then do contents <- readFile fileName
                    putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
                else do putStrLn "The file doesn't exist!"
```

由於 `doesFileExist` 的型態是 `doesFileExist :: FilePath -> IO Bool`，所以我們要寫成 `fileExists <- doesFileExist fileName`。那代表他回傳含有一個布林值告訴我們檔案存不存在的 I/O action。`doesFileExist` 是不能直接在 if expression 中使用的。

另一個解法是使用 Exception。在這個情境下使用 Exception 是沒問題的。檔案不存在這個 Exception 是在 I/O 中被丟出，所以在 I/O 中接起來也沒什麼不對。

要這樣使用 Exception，我們必須使用 `System.IO.Error` 中的 **catch** 函數。他的型態是 `catch :: IO a -> (IOError -> IO a) -> IO a`。他接受兩個參數，第一個是一個 I/O action。像是他可以接受一個打開檔案的 I/O action。第二個是 handler。如果第一個參數的 I/O action 丟出了 Exception，則他會被傳給 handler，他會決定要作些什麼。所以整個 I/O action 的結果不是如預期中做完第一個參數的 I/O action，就是 handler 處理的結果。

![](/files/-LjUmt7sw67lTtlmiHWh)

如果你對其他語言像是 Java, Python 中 try-catch 的形式很熟，那 `catch` 其實跟他們很像。第一個參數就是其他語言中的 try block。第二個參數就是其他語言中的 catch block。其中 handler 只有在 exception 被丟出時才會被執行。

handler 接受一個 `IOError` 型態的值，他代表的是一個 I/O exception 已經發生了。他也帶有一些 exception 本身的資訊。至於這型態在語言中使如何被實作則是要看編譯器。這代表我們沒辦法用 pattern matching 的方式來檢視 `IOError`。就像我們不能用 pattern matching 來檢視 `IO something` 的內容。但我們能用一些 predicate 來檢視他們。

我們來看看一個展示 `catch` 的程式

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e = putStrLn "Whoops, had some trouble!"
```

首先你看到我們可以在關鍵字周圍加上 backticks 來把 `catch` 當作 infix function 用，因為他剛好接受兩個參數。這樣使用讓可讀性變好。``toTry `catch` handler`` 跟 `catch toTry handler` 是一模一樣的。`toTry` 是一個 I/O action，而 `handler` 接受一個 `IOError`，並回傳一個當 exception 發生時被執行的 I/O action。

來看看執行的結果。

```haskell
$ runhaskell count_lines.hs i_exist.txt
The file has 3 lines!

$ runhaskell count_lines.hs i_dont_exist.txt
Whoops, had some trouble!
```

在 handler 裡面我們並沒有檢查我們拿到的是什麼樣的 `IOError`，我們只是打印出 `"Whoops, had some trouble!"`。接住任何種類的 Exception 就跟其他語言一樣，在 Haskell 中也不是一個好的習慣。假如其他種類的 Exception 發生了，好比說我們送一個中斷指令，而我們沒有接到的話會發生什麼事？這就是為什麼我們要做跟其他語言一樣的事：就是檢查我們拿到的是什麼樣的 Exception。如果說是我們要的 Exception，那就做對應的處理。如果不是，我們再重新丟出 Exception。我們把我們的程式這樣修改，只接住檔案不存在的 Exception。

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e = putStrLn "The file doesn't exist!"
    | otherwise = ioError e
```

除了 handler 以外其他東西都沒變，我們只接住我們想要的 I/O exception。這邊使用了 `System.IO.Error` 中的函數 **isDoesNotExistError** 跟 **ioError**。`isDoesNotExistError` 是一個運作在 `IOError` 上的 predicate ，他代表他接受一個 `IOError` 然後回傳 `True` 或 `False`，他的型態是 `isDoesNotExistError :: IOError -> Bool`。我們用他來判斷是否這個錯誤是檔案不存在所造成的。我們這邊使用 guard，但其實也可以用 if else。如果 exception 不是由於檔案不存在所造成的，我們就用 `ioEroror` 重新丟出接到的 exception。他的型態是 `ioError :: IOException -> IO a`，所以他接受一個 `IOError` 然後產生一個會丟出 exception 的 I/O action。那個 I/O action 的型態是 `IO a`，但他其實不會產生任何結果，所以他可以被當作是 `IO anything`。

所以有可能在 `toTry` 裡面丟出的 exception 並不是檔案不存在造成的，而 ``toTry `catch` handler`` 會接住再丟出來，很酷吧。

程式裡面有好幾個運作在 `IOError` 上的 I/O action，當其中一個沒有被 evaluate 成 `True` 時，就會掉到下一個 guard。這些 predicate 分別為：

* **isAlreadyExistsError**
* **isDoesNotExistError**
* **isFullError**
* **isEOFError**
* **isIllegalOperation**
* **isPermissionError**
* **isUserError**

大部分的意思都是顯而易見的。當我們用了 **userError** 來丟出 exception 的時候，`isUserError` 被 evaluate 成 `True`。例如說，你可以寫 `ioError $ userError "remote computer unplugged!"`，儘管用 `Either` 或 `Maybe` 來表示可能的錯誤會比自己丟出 exception 更好。

所以你可能寫一個像這樣的 handler

```haskell
handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e = putStrLn "The file doesn't exist!"
    | isFullError e = freeSomeSpace
    | isIllegalOperation e = notifyCops
    | otherwise = ioError e
```

其中 `notifyCops` 跟 `freeSomeSpace` 是一些你定義的 I/O action。如果 exception 不是你要的，記得要把他們重新丟出，不然你的程式可能只會安靜地當掉。

`System.IO.Error` 也提供了一些能詢問 exception 性質的函數，像是哪些 handle 造成錯誤，或哪些檔案名造成錯誤。這些函數都是 `ioe` 當開頭。而且你可以在文件中看到一整串詳細資料。假設我們想要打印出造成錯誤的檔案名。我們不能直接打印出從 `getArgs` 那邊拿到的 `fileName`，因為只有 `IOError` 被傳進 handler 中，而 handler 並不知道其他事情。一個函數只依賴於他所被呼叫時的參數。這也是為什麼我們會用 **ioeGetFileName** 這函數，他的型態是 `ioeGetFileName :: IOError -> Maybe FilePath`。他接受一個 `IOError` 並回傳一個 `FilePath`（他是 `String` 的同義詞。）基本上他做的事就是從 `IOError` 中抽出檔案路徑。我們來修改一下我們的程式。

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
    contents <- readFile fileName
    putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e =
        case ioeGetFileName e of Just path -> putStrLn $ "Whoops! File does not exist at: " ++ path
                                 Nothing -> putStrLn "Whoops! File does not exist at unknown location!"
    | otherwise = ioError e
```

在 `isDoesNotExistError` 是 `True` 的 guard 裡面，我們在 case expression 中用 `e` 來呼叫 `ioeGetFileName`，然後用 pattern matching 拆出 `Maybe` 中的值。當你想要用 pattern matching 卻又不想要寫一個新的函數的時候，case expression 是你的好朋友。

你不想只用一個 `catch` 來接你 I/O part 中的所有 exception。你可以只在特定地方用 `catch` 接 exception，或你可以用不同的 handler。像這樣：

```haskell
main = do toTry `catch` handler1
          thenTryThis `catch` handler2
          launchRockets
```

這邊 `toTry` 使用 `handler1` 當作 handler，而 `thenTryThis` 用了 `handler2`。`launchRockets` 並不是 `catch` 的參數，所以如果有任何一個 exception 被丟出都會讓我們的程式當掉，除非 `launchRockets` 使用 `catch` 來處理 exception。當然 `toTry`，`thenTryThis` 跟 `launchRockets` 都是 I/O actions，而且被 do syntax 綁在一起。這很像其他語言中的 try-catch blocks，你可以把一小段程式用 try-catch 包住，你可以自己調整該包多少進去。

現在你知道如何處理 I/O exception 了。我們並沒有提到如何從 pure code 中丟出 exception，這是因為正如我們先前提到的，Haskell 提供了更好的辦法來處理錯誤。就算是在可能會失敗的 I/O action 中，我也傾向用 `IO (Either a b)`，代表他們是 I/O action，但當他們被執行，他們結果的型態是 `Either a b`，意思是不是 `Left a` 就是 `Right b`。


# ch10


# 函數式地思考來解決問題

在這一章中，我們會檢視幾個有趣的問題，並嘗試用函數式的方式來漂亮地解決他們。我們並不會介紹新的概念，我們只是練習我們剛學到的寫程式的技巧。每一節都會探討不同的問題。會先描述問題，然後用最好的方式解決他。

## 運算逆波蘭表示法(Reverse Polish notation form)

我們在學校學習數學的時候，我們多半都是用中置(infix)的方式來寫數學式。例如說，我們會寫 `10 - (4 + 3) * 2`。`+`, `*`, `-` 是中置運算子(infix operators)。在 Haskell 中就像是 `+` 或 `elem` 一樣。這種寫法對於人類來說很容易閱讀與理解，但缺點是我們必須用括號來描述運算的優先順序。

逆波蘭表示法是另外一種數學式的描述方法。乍看之下顯得怪異，但他其實很容易理解並使用。因為我們不需要括弧來描述，也很容易放進計算機裡面運算。儘管現在的計算機都是用中置的方式讓你輸入，有些人仍堅持用 RPN 的計算機。前述的算式如果表達成 RPN 的話會是 `10 4 3 + 2 * -`。我們要如何計算他的結果呢？可以想想堆疊，基本上你是從左向右閱讀算式。每當碰到一個數值，就把他堆上堆疊。當我們碰到一個運算子。就把兩個數值從堆疊上拿下來，用運算子運算兩個數值然後把結果推回堆疊中。當你消耗完整個算式的時候，而且假設你的算式是合法的，那你就應該只剩一個數值在堆疊中，

![](/files/-LjUmqYcwyeMu_FD6OSe)

我們再接著看 `10 4 3 + 2 * -`。首先我們把 `10` 推到堆疊上，所以堆疊現在是 `10`。下一個接著的輸入是 `4`，我們也把他推上堆疊。堆疊的狀態便變成 `10, 4`。接著也對下一個輸入 `3` 做同樣的事，所以堆疊變成 `10, 4, 3`。然後便碰到了第一個運算子 `+`。我們把堆疊最上層的兩個數值取下來（所以堆疊變成 `10`）把兩個數值加起來然後推回堆疊上。堆疊的狀態便變成 `10, 7`。我們再把輸入 `2` 推上堆疊，堆疊變成 `10, 7, 2`。我們又碰到另一個運算子，所以把 `7` 跟 `2` 取下，把他們相乘起來然後推回堆疊上。`7` 跟 `2` 相乘的結果是 `14`，所以堆疊的狀態是 `10, 14`。最後我們碰到了 `-`。我們把 `10` 跟 `14` 取下，將他們相減然後推回堆疊上。所以現在堆疊的狀態變成 `-4`。而我們已經把所有數值跟運算子的消耗完了，所以 `-4` 便是我們的結果。

現在我們知道我們如何手算 RPN 運算式了，接下來可以思考一下我們寫一個 Haskell 的函數，當他接到一個 RPN 運算式，像是 `"10 4 3 + 2 * -"` 時，他可以給出結果。

這個函數的型別會是什麼樣呢？我們希望他接受一個字串當作參數，並產出一個數值作為結果。所以應該會是 `solveRPN :: (Num a) => String -> a`。

```
小建議：在你去實作函數之前，先想一下你會怎麼宣告這個函數的型別能夠幫助你釐清問題。在 Haskell 中由於我們有夠強的型別系統，光從函數的宣告就可以得到許多資訊。
```

![](/files/-LjUmqYhFMnYTKNgPUTa)

當我們要實作一個問題的解法時，你可以先動手一步一步解看看，嘗試從裡面得到一些靈感。我們這邊把每一個用空白隔開的數值或運算子都當作獨立的一項。所以把 `"10 4 3 + 2 * -"` 這樣一個字串斷成一串 list `["10","4","3","+","2","*","-"]` 應該會有幫助。

接下來我們要如何應用這個斷好的 list 呢？我們從左至右來走一遍，並保存一個工作用的堆疊。這樣有讓你想到些什麼可以用的嗎？沒錯，在 `folds` 的那一章裡面，我們提到基本上當你需要從左至右或由右至左走過一遍 list 的時候並產生些結果的時候。我們都能用 fold 來實作他。

在這個 case 中由於我們是從左邊走到右邊，所以我們採取 left fold。accumulator 則是選用堆疊，而 fold 的結果也會是一個堆疊，只是裡面只有一個元素而已。

另外要多考慮一件事是我們用什麼來代表我們的堆疊？我們可以用 list 來代替，list 的 head 就可以當作是堆疊的頂端。畢竟要把一個元素加到 list 的 head 要比加到最後要有效率多。所以如果我們有一個堆疊，裡面有 `10, 4, 3`，那我們可以用 `[3,4,10]` 來代表他。

現在我們有了足夠的資訊來寫出我們的函數。他會接受一個字串 `"10 4 3 + 2 * -"`，隨即用 `words` 來斷成 list `["10","4","3","+","2","*","-"]`。接下來我們做一個 left fold 來產生出只有一個元素的堆疊，也就是 `[-4]`。我們把這個元素從 list 取出便是最後的結果。

來看看我們的實作：

```haskell
import Data.List  

solveRPN :: (Num a) => String -> a  
solveRPN expression = head (foldl foldingFunction [] (words expression))  
    where   foldingFunction stack item = ...
```

我們接受一個運算式並把他斷成一串 List。然後我們用一個 folding 函數來 fold 這串 list。注意到我們用 `[]` 來當作起始的 accumulator。這個 accumulator 就是我們的堆疊，所以 `[]` 代表一個空的堆疊。在運算之後我們得到一個只有一個元素的堆疊，我們呼叫 `head` 來取出他並用 `read` 來轉換他。

所以我們現在只缺一個接受堆疊的 folding 函數，像是可以接受 `[4,10]` 跟 `"3"`，然後得到 `[3,4,10]`。如果是 `[4,10]` 跟 `"*"`，那就會得到 `[40]`。但在實作之前，我們先把我們的函數改寫成 point-free style，這樣可以省下許多括號。

```haskell
import Data.List  

solveRPN :: (Num a) => String -> a  
solveRPN = head . foldl foldingFunction [] . words  
      where   foldingFunction stack item = ...
```

看起來好多了。我們的 folding 函數會接受一個堆疊、新的項，並回傳一個新的堆疊。我們使用模式匹配的方式來取出堆疊最上層的元素，然後對 `"*"` 跟 `"-"` 做匹配。

```haskell
solveRPN :: (Num a, Read a) => String -> a  
solveRPN = head . foldl foldingFunction [] . words  
    where   foldingFunction (x:y:ys) "*" = (x * y):ys  
            foldingFunction (x:y:ys) "+" = (x + y):ys  
            foldingFunction (x:y:ys) "-" = (y - x):ys  
            foldingFunction xs numberString = read numberString:xs
```

我們用展開成四個模式匹配。模式會從第一個開始嘗試匹配。所以 folding 函數會看看目前的項是否是 `"*"`。如果是，那就會將 `[3,4,9,3]` 的頭兩個元素綁定到 `x`，`y` 兩個名稱。所以 `x` 會是 `3` 而 `y` 等於 `4`。`ys` 便會是 `[9,3]`。他會回傳一個 list，只差在 `x` 跟 `y` 相乘的結果為第一個元素。也就是說會把最上層兩個元素取出，相乘後再放回去。如果第一個元素不是 `"*"`，那模式匹配就會比對到 `"+"`，以此類推。

如果項並未匹配到任何一個運算子，那我們就會假設這個字串是一個數值。如果他是一個數值，我們會用 `read` 來把字串轉換成數值。並把這個數值推到堆疊上。

另外注意到我們加了 `Read a` 這像 class constraint，畢竟我們要使用到 `read` 來轉換成數值。所以我們必須要宣告成他要屬於 `Num` 跟 `Read` 兩種 typeclass。（譬如說 `Int`,`Float` 等）

我們是從左至右走過 `["2","3","+"]`。一開始堆疊的狀態是 `[]`。首先他會用 `[]` 跟 `"2"` 來餵給 folding 函數。由於此項並不是一個運算子。他會用 `read` 讀取後加到 `[]` 的開頭。所以堆疊的狀態變成 `[2]`。接下來就是用 `[2]` 跟 `["3"]` 來餵給 folding 函數，而得到 `[3,2]`。最後再用 `[3,2]` 跟 `"+"` 來呼叫 folding 函數。這會堆疊頂端的兩個數值，加起來後推回堆疊。最後堆疊變成 `[5]`，這就是我們回傳的數值。

我們來試試看我們新寫的函數：

```haskell
ghci> solveRPN "10 4 3 + 2 * -"  
-4  
ghci> solveRPN "2 3 +"  
5  
ghci> solveRPN "90 34 12 33 55 66 + * - +"  
-3947  
ghci> solveRPN "90 34 12 33 55 66 + * - + -"  
4037  
ghci> solveRPN "90 34 12 33 55 66 + * - + -"  
4037  
ghci> solveRPN "90 3 -"  
87
```

看起來運作良好。這個函數有一個特色就是他很容易改寫來支持額外的運算子。他們也不一定要是二元運算子。例如說我們可以寫一個運算子叫做 `"log"`，他會從堆疊取出一個數值算出他的 log 後推回堆疊。我們也可以用三元運算子來從堆疊取出三個數值，並把結果放回堆疊。甚至是像是 `"sum"` 這樣的運算子，取出所有數值並把他們的和推回堆疊。

我們來改寫一下我們的函數讓他多支援幾個運算子。為了簡單起見，我們改寫宣告讓他回傳 `Float` 型別。

```haskell
import Data.List  

solveRPN :: String -> Float  
solveRPN = head . foldl foldingFunction [] . words  
where   foldingFunction (x:y:ys) "*" = (x * y):ys  
        foldingFunction (x:y:ys) "+" = (x + y):ys  
        foldingFunction (x:y:ys) "-" = (y - x):ys  
        foldingFunction (x:y:ys) "/" = (y / x):ys  
        foldingFunction (x:y:ys) "^" = (y ** x):ys  
        foldingFunction (x:xs) "ln" = log x:xs  
        foldingFunction xs "sum" = [sum xs]  
        foldingFunction xs numberString = read numberString:xs
```

看起來不錯，沒有疑問地 `/` 是除法而 `**` 是取 exponential。至於 log 運算子，我們只需要模式匹配一個元素，畢竟 log 只需要一個元素。而 sum 運算子，我們只回傳一個僅有一個元素的堆疊，包含了所有元素的和。

```haskell
ghci> solveRPN "2.7 ln"  
0.9932518  
ghci> solveRPN "10 10 10 10 sum 4 /"  
10.0  
ghci> solveRPN "10 10 10 10 10 sum 4 /"  
12.5  
ghci> solveRPN "10 2 ^"  
100.0
```

由於 `read` 知道如何轉換浮點數，我們也可在運算適中使用他。

```haskell
ghci> solveRPN "43.2425 0.5 ^"  
6.575903
```

有這樣一個容易拓展到浮點數而且動到的程式碼又在十行以內的函數，我想是非常棒的。

有一件事要留意的是這個函數對於錯誤處理並不好。當我們碰到非法輸入的時候，他就會直接當掉。之後我們碰到 Monad 的時候我們會寫一個容錯的版本，他的型別會是 `solveRPN :: String -> Maybe Float`。當然我們現在也可以寫一個，不過那會有點麻煩，因為會有一大堆檢查 `Nothing` 的動作。如果你希望挑戰的話，也可以盡管嘗試。（提示：你可以用 `reads` 來看看一次 read 是否會成功）

## 路徑規劃

我們接下來的問題是：你的飛機剛剛降落在英格蘭的希思羅機場。你接下來有一個會議，你租了一台車希望盡速從機場前往倫敦市中心。

從希思羅機場到倫敦有兩條主要道路，他們中間有很多小路連接彼此。如果你要走小路的話都會花掉一定的時間。你的問題就是要選一條最佳路徑讓你可以盡快前往倫敦。你從圖的最左邊出發，中間可能穿越小路來前往右邊。

![](/files/-LjUmqYjkzo_gqdxX61x)

你可以從圖中看到，從希思羅機場到倫敦在這個路徑配置下的最短路徑是先選主要道路 B，經由小路到 A 之後，再走一小段，轉到 B 之後繼續往前走。如果採取這個路徑的話，會花去 75 分鐘。如果選其他道路的話，就會花更多時間。

我們任務就是要寫一個程式，他接受道路配置的輸入，然後印出對應的最短路徑。我們的輸入看起來像是這樣：

```haskell
50  
10  
30  
5  
90  
20  
40  
2  
25  
10  
8  
0
```

我們在心中可以把輸入的數值三個三個看作一組。每一組由道路 A,道路 B,還有交叉的小路組成。而要能夠這樣組成，我們必須讓最後有一條虛擬的交叉小路，只需要走 0 分鐘就可以穿越他。因為我們並不會在意在倫敦裡面開車的成本，畢竟我們已經到達倫敦了。

正如我們在解 RPN 計算機的問題的時候，我們是用三步驟來解題：

* 首先忘掉 Haskell，想想我們自己是怎麼一步步解題的。
* 想想如何在 Haskell 中表達我們的資料。
* 在 Haskell 中要如何對這些資料做運算來產生出解答。

在介紹 RPN 計算機的章節中，我們首先自己用人腦計算表達式，在心中維持一個堆疊然後一項一項處理。我們決定用一個字串來表達我們的表達式。最後，我們用 left fold 來走過我們這一串 list，並算出結果。

究竟我們要怎麼用手算出從希思羅機場到倫敦的最短路徑呢？我們可以觀察整章圖片，猜測哪一條是最短路徑然後希望我們有猜對。這樣的作法對於很小的輸入可以成功，但如果我們的路徑超過 10000 組呢？這樣我們不知道我們的解法是不是最佳解，我們只能說可能是。

所以那並不是一個好作法。這邊有一張簡化過後的圖。

![](/files/-LjUmqYlRt-UhCR_PSgz)

你能想出來到道路 A 上第一個交叉點的最短路徑嗎？（標記成 A1 的點）這太容易了。我們只要看看從道路 A 出發或是從道路 B 出發穿越至道路 A 兩種作法哪種比較短就好。很明顯的，從道路 B 出發的比較短，只要花費 40 分鐘，然而從道路 A 則要花費 50 分鐘。那到交叉點 B1 呢？同樣的作法可以看出從道路 B 出發只要花費 10 分鐘，遠比從道路 A 出發然後穿越小路要花費少，後者要花費 80 分鐘！

現在我們知道要到達 A1 的最短路徑是經由 B 然後鄒小路到達，共花費 40。而我們知道要達到 B1 的最短路徑則是直接走 B，花費 10。這樣的知識有辦法幫助我們得知到下一個交叉點的最短路徑嗎？可以的。

我們來看看到達 A2 的最短路徑是什麽。要到達 A2，我們必須要從 A1 走到 A2 或是從 B1 走小路。由於我們知道到達 A1 跟 B1 的成本，我們可以很容易的想出到達 A2 的最佳路徑。到達 A1 要花費 40，而從 A1 到 A2 需要 5。所以 `B, C, A` 總共要 45。而要到達 B1 只要 10，但需要額外花費 110 分鐘來到達 B2 然後走小路到達 A2。所以最佳路徑就是 `B, C, A`。同樣地到達 B2 最好的方式就是走 A1 然後走小路。

```
也許你會問如果先在 B1 跨到道路 A 然後走到 A2 的情況呢？我們已經考慮過了從 B1 到 A1 的情況，所以我們不需要再把他考慮進去。
```

現在我們有了至 A2 跟 B2 的最佳路徑，我們可以一直重複這個過程直到最右邊。一旦我們到達了 A4 跟 B4，那其中比較短的就是我們的最佳路徑了。

基本上對於第二組而言，我們只是不斷地重複之前的步驟，只是我們考慮進在前面的最佳路徑而已。當然我們也可以說在第一步就考慮進了前面的最佳路徑，只是他們都是 0 而已。

總結一下。要得到從希思羅機場到倫敦的最短路徑。我們首先看看到達下一個道路 A 上的交叉點的最短路徑。共有兩種選擇的路徑，一是直接從道路 A 出發然後走到交叉點，要不然就是從道路 B 出發，走到第一個交叉點然後走小路。得到結果後記住結果。接著再用同樣的方法來得到走到道路 B 上下一個交叉點的最短路徑，並也記住結果。然後我們看看要走到再下一個道路 A 上的交叉點，究竟是從這個道路 A 上的交叉點往前走，或是從對應的道路 B 上的交叉點往前走再走到對面，兩種選擇哪種比較好。記下比較好的選擇，然後也對對應的道路 B 上的交叉點做一次這個過程。做完全部組之後就到達最右邊。一旦到達最右邊，最佳的選擇就是我們的最短路徑了。

基本上當我們到達最右邊的時候，我們記下了最後停在道路 A 的最短路徑跟最後停在道路 B 的最短路徑。其中比較短的是我們真正的最短路徑。現在我們已經知道怎麼用手算出答案。如果你有閒工夫，你可以拿紙筆對於任何一組道路配置算出他的最短路徑。

接下來的問題是，我們要如何用 Haskell 的型別來代表這裡的道路配置呢？一種方式就是把起始點跟交叉點都當作圖的節點，並連到其他的交叉點。如果我們想像其實起點也有一條長度為 1 的虛擬道路連接彼此，那每個交叉點或是節點就都連接對面的節點了。同時他們也連到下一個交叉點。唯一的例外是最後一個節點，他們只連接到對面。

```haskell
data Node = Node Road Road | EndNode Road  
data Road = Road Int Node
```

一個節點要碼是一個普通的節點，他包含有通往下一個交叉點的路徑資訊，還有往對面道路的資訊。或是一個終端點，只包含往對面節點的道路資訊。一條道路包含他多長，還有他指向哪裡。例如說，道路 A 的第一個部份就可寫成 `Road 50 a1`。其中 `a1` 是 `Node x y` 這樣一個節點。而 `x` 跟 `y` 則分別指向 B1 跟 A2。

另一種方式就是用 `Maybe` 來代表往下一個交叉點走的路。每個節點有指到對面節點的路徑部份，但只有不是終端節點的節點才有指向下一個交叉點的路。

```haskell
data Node = Node Road (Maybe Road)  
data Road = Road Int Node
```

這些是用 Haskell 來代表道路系統的方式，而我們也能靠他們來解決問題。但也許我們可以想出更簡單的模型？如果我們想想之前手算的方式，我們每次檢查都只有檢查三條路徑的長度而已。在道路 A 的部份，跟在道路 B 的部份，還有接觸兩個部份並將他們連接起來的部份。當我們觀察到 A1 跟 B1 的最短路徑時，我們只考慮第一組的三個部份，他們分別花費 50, 10 跟 30。所以道路系統可以用四組來表示：`50, 10, 30`，`5, 90, 20`，`40, 2, 25` 跟 `10, 8, 0`。

讓我們資料型別越簡單越好，不過這樣已經是極限了。

```haskell
data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show)  
type RoadSystem = [Section]
```

這樣很完美，而且對於我們的實作也有幫助。`Section` 是一個 algebraic data type，包含三個整數，分別代表三個不同部份的道路長。我們也定義了型別同義字，說 `RoadSystem` 代表包含 section 的 list。

```
當然我們也可以用一個 tuple ``(Int, Int, Int)`` 來代表一個 section。使用 tuple 對於一些簡單的情況是比較方便，但對於比較複雜的情況定義自己的 algebraic data type 會比較好。他讓型別系統獲得比較多的資訊。``(Int, Int, Int)`` 畢竟也可能被使用在定義三維空間中的一個向量，只用 tuple 讓我們可能把這兩種情形混雜起來使用。如果我們用 ``Section`` 跟 ``Vector`` 的話就不會不小心搞混了。
```

從希思羅機場到倫敦的道路系統便可以這樣表示：

```haskell
heathrowToLondon :: RoadSystem  
heathrowToLondon = [Section 50 10 30, Section 5 90 20, Section 40 2 25, Section 10 8 0]
```

我們現在要做的就是用 Haskell 實作我們先前的解法。所以我們應該怎樣宣告我們計算最短路徑函數的型別呢？他應該接受一個道路系統作為參數，然後回傳一個路徑。我們會用一個 list 來代表我們的路徑。我們定義了 `Label` 來表示 `A`, `B` 或 `C`。並且也定義一個同義詞 `Path`：

```haskell
data Label = A | B | C deriving (Show)  
type Path = [(Label, Int)]
```

而我們的函數 `optimalPath` 應該要有 `optimalPath :: RoadSystem -> Path` 這樣的型別。如果被餵給 `heathrowToLondon` 這樣的道路系統，他應該要回傳下列的路徑：

```haskell
[(B,10),(C,30),(A,5),(C,20),(B,2),(B,8)]
```

我們接下來就從左至右來走一遍 list，並沿路上記下 A 的最佳路徑跟 B 的最佳路徑。我們會 accumulate 我們的最佳路徑。這聽起來有沒有很熟悉？沒錯！就是 left fold。

當我們手動做解答的時候，有一個步驟是我們不斷重複的。就是檢查現有 A 跟 B 的最佳路徑以及目前的 section，產生出新的 A 跟 B 的最佳路徑。舉例來說，最開始我們的最佳路徑是 `[]` 跟 `[]`。我們看過 `Section 50 10 30` 後就得到新的到 A1 的最佳路徑為 `[(B,10),(C,30)]`，而到 B1 的最佳路徑是 `[(B,10)]`。如果你們把這個步驟看作是一個函數，他接受一對路徑跟一個 section，並產生出新的一對路徑。所以型別是 `(Path, Path) -> Section -> (Path, Path)`。我們接下來繼續實作這個函數。

```
提示：把 ``(Path, Path) -> Section -> (Path, Path)`` 當作 left fold 用的二元函數，fold 要求的型態是 ``a -> b -> a``。
```

```haskell
roadStep :: (Path, Path) -> Section -> (Path, Path)  
roadStep (pathA, pathB) (Section a b c) =   
    let priceA = sum $ map snd pathA  
        priceB = sum $ map snd pathB  
        forwardPriceToA = priceA + a  
        crossPriceToA = priceB + b + c  
        forwardPriceToB = priceB + b  
        crossPriceToB = priceA + a + c  
        newPathToA = if forwardPriceToA <= crossPriceToA  
                        then (A,a):pathA  
                        else (C,c):(B,b):pathB  
        newPathToB = if forwardPriceToB <= crossPriceToB  
                        then (B,b):pathB  
                        else (C,c):(A,a):pathA  
    in  (newPathToA, newPathToB)
```

![](/files/-LjUmqYpsWRiBdzBJJ2E)

上面的程式究竟寫了些什麽？首先他根據先前 A 的最佳解計算出道路 A 的最佳解，之後也如法炮製計算 B 的最佳解。使用 `sum $ map snd pathA`，所以如果 `pathA` 是 `[(A,100),(C,20)]`。`priceA` 就是 `120`。`forwardPriceToA` 就會是我們要付的成本。如果我們是從先前在 A 上的交叉點前往。那他就會等於我們至先前交叉點的最佳解加上目前 section 中 A 的部份。`crossPriceToA` 則是我們從先前在 B 上的交叉點前往 A 所要付出的代價。他是先前 B 的最佳解加上 section 中 B 的部份加上 C 的長。同樣地方式也可以決定 `forwardPriceToB` 跟 `crossPriceToB`。

現在我們知道了到 A 跟 B 的最佳路徑，我們需要根據這些資訊來構造到 A 跟 B 的整體路徑。如果直接走到 A 耗費較少的話，我們就把 `newPathToA` 設定成 `(A,a):pathA`。這樣做的事就是把 `Label` `A` 跟 section 的長度 `a` 接到最佳路徑的前面。要記得 `A` 是一個 label，而 `a` 的型別是 `Int`。我們為什麽要接在前面而不是 `pathA ++ [(A,a)]` 呢？因為接在 list 的前面比起接在後端要有效率多了。不過這樣產生出來的 list 就會相反。但要把 list 再反過來並不難。如果先走到 B 再穿越小路走到 A 比較短的話，那 `newPathToA` 就會包含這樣走的路線。同樣的道理也可以套用在 `newPathToB` 上。

最後我們回傳 `newPathToA` 跟 `newPathToB` 這一對結果。

我們把 `heathrowToLondon` 的第一個 section 餵給我們的函數。由於他是第一個 section，所以到 A 跟 B 的最佳路徑就是一對空的 list。

```haskell
ghci> roadStep ([], []) (head heathrowToLondon)  
([(C,30),(B,10)],[(B,10)])
```

要記住包含的路徑是反過來的，要從右邊往左邊讀。所以到 A 的最佳路徑可以解讀成從 B 出發，然後穿越到道路 A。而 B 的最佳路徑則是直接從 B 出發走到下一個交叉點。

```
優化小技巧：當我們寫 ``priceA = sum $ map snd pathA`` 的時候。我們是在計算每步的成本。如果我們實作 ``roadStep`` 成 ``(Path, Path, Int, Int) -> Section -> (Path, Path, Int, Int)`` 的話就可以不必那麼做。其中的整數型別代表 A 跟 B 上的最小成本。
```

現在我們有了一個函數他接受一對路徑跟一個 section，並產生新的最佳路徑。我們可以用一個 left fold 來做。我們用 `([],[])` 跟第一個 section 來餵給 `roadStep` 並得到一對最佳路徑。然後他又被餵給這個新得到的最佳路徑跟下一個 section。以此類推。當我們走過全部的 section 的時候，我們就會得到一對最佳路徑，而其中比較短的那個就是解答。有了這樣的想法，我們便可以實作 `optimalPath`。

```haskell
optimalPath :: RoadSystem -> Path  
optimalPath roadSystem = 
    let (bestAPath, bestBPath) = foldl roadStep ([],[]) roadSystem  
    in  if sum (map snd bestAPath) <= sum (map snd bestBPath)  
                then reverse bestAPath  
                else reverse bestBPath
```

我們對 `roadSystem` 做 left fold。而用的起始 accumulator 是一對空的路徑。fold 的結果也是一對路徑，我們用模式匹配的方式來把路徑從結果取出。然後我們檢查哪一個路徑比較短便回傳他。而且在回傳之前也順便把整個結果反過來。因為我們先前提到的我們是用接在前頭的方式來構造結果的。

我們來測試一下吧！

```haskell
ghci> optimalPath heathrowToLondon  
[(B,10),(C,30),(A,5),(C,20),(B,2),(B,8),(C,0)]
```

這正是我們應該得到的結果！不過跟我們預期的結果仍有點差異，在最後有一步 `(C,0)`，那代表我們已經在倫敦了仍然跨越小路。不過由於他的成本是 0，所以依然可以算做正確的結果。

我們找出最佳路徑的函數，現在要做的只需要從標準輸入讀取文字形式道路系統，並把他轉成 `RoadSystem`，然後用 `optimalPath` 來把他跑一遍就好了。

首先，我們寫一個函數，他接受一串 list 並把他切成同樣大小的 group。我們命名他為 `groupOf`。當參數是 `[1..10]` 時，`groupOf 3` 應該回傳 `[[1,2,3],[4,5,6],[7,8,9],[10]]`。

```haskell
groupsOf :: Int -> [a] -> [[a]]  
groupsOf 0 _ = undefined  
groupsOf _ [] = []  
groupsOf n xs = take n xs : groupsOf n (drop n xs)
```

一個標準的遞迴函數。對於 `xs` 等於 `[1..10]` 且 `n` 等於 `3`，這可以寫成 `[1,2,3] : groupsOf 3 [4,5,6,7,8,9,10]`。當這個遞迴結束的時候，我們的 list 就三個三個分好組。而下列是我們的 `main` 函數，他從標準輸入讀取資料，構造 `RoadSystem` 並印出最短路徑。

```haskell
import Data.List  

main = do  
    contents <- getContents  
    let threes = groupsOf 3 (map read $ lines contents)  
        roadSystem = map (\[a,b,c] -> Section a b c) threes  
        path = optimalPath roadSystem  
        pathString = concat $ map (show . fst) path  
        pathPrice = sum $ map snd path  
    putStrLn $ "The best path to take is: " ++ pathString  
    putStrLn $ "The price is: " ++ show pathPrice
```

首先，我們從標準輸入獲取所有的資料。然後我們呼叫 `lines` 來把 `"50\n10\n30\n...` 轉換成 `["50","10","30"..`，然後我們 map `read` 來把這些轉成包含數值的 list。我們呼叫 `groupsOf 3` 來把 list 的 list，其中子 list 長度為 3。我們接著對這個 list 來 map 一個 lambda `(\[a,b,c] -> Section a b c)`。正如你看到的，這個 lambda 接受一個長度為 3 的 list 然後把他變成 Section。所以 `roadSystem` 現在就是我們的道路配置，而且是正確的型別 `RoadSystem`。我們呼叫 `optimalPath` 而得到一個路徑跟對應的代價，之後再印出來。

我們將下列文字存成檔案。

```haskell
50  
10  
30  
5  
90  
20  
40  
2  
25  
10  
8  
0
```

存成一個叫 `paths.txt` 的檔案然後餵給我們的程式。

```haskell
$ cat paths.txt | runhaskell heathrow.hs  
The best path to take is: BCACBBC  
The price is: 75
```

執行成功！你可以用你對 `Data.Random` 的瞭解來產生一個比較大的路徑配置，然後你可以把產生的亂數資料餵給你的程式。如果你碰到堆疊溢出，試試看用 `foldl'` 而不要用 `foldl`。`foldl'` 是 strict 的可以減少記憶體消耗。


# ch11


# Functors, Applicative Functors 與 Monoids

Haskell 的一些特色，像是純粹性，高階函數，algebraic data types，typeclasses，這些讓我們可以從更高的角度來看到 polymorphism 這件事。不像 OOP 當中需要從龐大的型態階層來思考。我們只需要看看手邊的型態的行為，將他們跟適當地 typeclass 對應起來就可以了。像 `Int` 的行為跟很多東西很像。好比說他可以比較相不相等，可以從大到小排列，也可以將他們一一窮舉出來。

Typeclass 的運用是很隨意的。我們可以定義自己的資料型態，然後描述他可以怎樣被操作，跟 typeclass 關聯起來便定義了他的行為。由於 Haskell 強大的型態系統，這讓我們只要讀函數的型態宣告就可以知道很多資訊。typeclass 可以定義得很抽象很 general。我們之前有看過 typeclass 定義了可以比較兩個東西是否相等，或是定義了可以比較兩個東西的大小。這些是既抽象但又描述簡潔的行為，但我們不會認為他們有什麼特別之處，因為我們時常碰到他們。最近我們看過了 functor，基本上他們是一群可以被 map over 的物件。這是其中一個例子能夠抽象但又漂亮地描述行為。在這一章中，我們會詳加闡述 functors，並會提到比較強一些的版本，也就是 applicative functors。我們也會提到 monoids。

## 溫習 Functors

![](/files/-LjUmuXrKNNPsK9Uj7wV)

我們已經在之前的章節提到 functors。如果你還沒讀那個章節，也許你應該先去看看。或是你直接假裝你已經讀過了。

來快速複習一下：Functors 是可以被 map over 的物件，像是 lists，`Maybe`，trees 等等。在 Haskell 中我們是用 `Functor` 這個 typeclass 來描述他。這個 typeclass 只有一個 method，叫做 `fmap`，他的型態是 `fmap :: (a -> b) -> f a -> f b`。這型態說明了如果給我一個從 `a` 映到 `b` 的函數，以及一個裝了 `a` 的盒子，我會回給你一個裝了 `b` 的盒子。就好像用這個函數將每個元素都轉成 `b` 一樣

```
*給一點建議*。這盒子的比喻嘗試讓你抓到些 functors 是如何運作的感覺。在之後我們也會用相同的比喻來比喻 applicative functors 跟 monads。在多數情況下這種比喻是恰當的，但不要過度引申，有些 functors 是不適用這個比喻的。一個比較正確的形容是 functors 是一個計算語境（computational context）。這個語境可能是這個 computation 可能帶有值，或是有可能會失敗（像 ``Maybe`` 跟 ``Either a``），或是他可能有多個值（像 lists），等等。
```

如果一個 type constructor 要是 `Functor` 的 instance，那他的 kind 必須是 `* -> *`，這代表他必須剛好接受一個 type 當作 type parameter。像是 `Maybe` 可以是 Functor 的一個 instance，因為他接受一個 type parameter，來做成像是 `Maybe Int`，或是 `Maybe String`。如果一個 type constructor 接受兩個參數，像是 `Either`，我們必須給他兩個 type parameter。所以我們不能這樣寫：`instance Functor Either where`，但我們可以寫 `instance Functor (Either a) where`，如果我們把 `fmap` 限縮成只是 `Either a` 的，那他的型態就是 `fmap :: (b -> c) -> Either a b -> Either a c`。就像你看到的，`Either a` 的是固定的一部分，因為 `Either a` 只恰好接受一個 type parameter，但 `Either` 則要接受兩個 type parameters。這樣 fmap 的型態變成 `fmap :: (b -> c) -> Either b -> Either c`，這不太合理。

我們知道有許多型態都是 `Functor` 的 instance，像是 `[]`，`Maybe`，`Either a` 以及我們自己寫的 `Tree`。我們也看到了如何用一個函數 map 他們。在這一章節，我們再多舉兩個例子，也就是 `IO` 跟 `(->) r`。

如果一個值的型態是 `IO String`，他代表的是一個會被計算成 String 結果的 I/O action。我們可以用 do syntax 來把結果綁定到某個名稱。我們之前把 I/O action 比喻做長了腳的盒子，會到真實世界幫我們取一些值回來。我們可以檢視他們取了什麼值，但一旦看過，我們必須要把值放回盒子中。用這個比喻，`IO` 的行為就像是一個 functor。

我們來看看 `IO` 是怎麼樣的一個 `Functor` instance。當我們 `fmap` 用一個 function 來 map over I/O action 時，我們會想要拿回一個裝著已經用 function 映射過值的 I/O action。

```haskell
instance Functor IO where
    fmap f action = do
        result <- action
        return (f result)
```

對一個 I/O action 做 map over 動作的結果仍會是一個 I/O action，所以我們才用 do syntax 來把兩個 I/O action 黏成一個。在 `fmap` 的實作中，我們先執行了原本傳進的 I/O action，並把結果綁定成 `result`。然後我們寫了 `return (f result)`。`return` 就如你所知道的，是一個只會回傳包了你傳給他東西的 I/O action。還有一個 do block 的回傳值一定是他最後一個 I/O action 的回傳值。這也是為什麼我們需要 return。其實他只是回傳包了 `f result` 的 I/O action。

我們可以再多實驗一下來找到些感覺。來看看這段 code：

```haskell
main = do line <- getLine   
        let line' = reverse line  
        putStrLn $ "You said " ++ line' ++ " backwards!"  
        putStrLn $ "Yes, you really said" ++ line' ++ " backwards!"
```

這程式要求使用者輸入一行文字，然後印出一行反過來的。 我們可以用 `fmap` 來改寫：

```haskell
main = do line <- fmap reverse getLine  
            putStrLn $ "You said " ++ line ++ " backwards!"  
            putStrLn $ "Yes, you really said" ++ line ++ " backwards!"
```

![](/files/-LjUmuXzznYwGcBsWpBE)

就像我們用 `fmap` `reverse` 來 map over `Just "blah"` 會得到 `Just "halb"`，我們也可以 `fmap` `reverse` 來 map over `getLine`。`getLine` 是一個 I/O action，他的 type 是 `IO String`，而用 `reverse` 來 map over 他會回傳一個取回一個字串並 `reverse` 他的 I/O action。就像我們 apply 一個 function 到一個 `Maybe` 一樣，我們也可以 apply 一個 function 到一個 `IO`，只是這個 `IO` 會跑去外面拿回某些值。然後我們把結果用 `<-` 綁定到某個名稱，而這個名稱綁定的值是已經 `reverse` 過了。

而 `fmap (++"!") getLine` 這個 I/O action 表現得就像 `getLine`，只是他的結果多了一個 `"!"` 在最後。

如果我們限縮 `fmap` 到 `IO` 型態上，那 fmap 的型態是 `fmap :: (a -> b) -> IO a -> IO b`。`fmap` 接受一個函數跟一個 I/O action，並回傳一個 I/O action 包含了已經 apply 過 function 的結果。

如果你曾經注意到你想要將一個 I/O action 綁定到一個名稱上，只是為了要 apply 一個 function。你可以考慮使用 `fmap`，那會更漂亮地表達這件事。或者你想要對 functor 中的資料做 transformation，你可以先將你要用的 function 寫在 top level，或是把他作成一個 lambda function，甚至用 function composition。

```haskell
import Data.Char  
import Data.List  

main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine  
          putStrLn line
```

```haskell
$ runhaskell fmapping_io.hs  
hello there  
E-R-E-H-T- -O-L-L-E-H
```

正如你想的，`intersperse '-' . reverse . map toUpper` 合成了一個 function，他接受一個字串，將他轉成大寫，然後反過來，再用 `intersperse '-'` 安插'-'。他是比較漂亮版本的 `(\xs -> intersperse '-' (reverse (map toUpper xs)))`。

另一個 `Functor` 的案例是 `(->) r`，只是我們先前沒有注意到。你可能會困惑到底 `(->) r` 究竟代表什麼？一個 `r -> a` 的型態可以寫成 `(->) r a`，就像是 `2 + 3` 可以寫成 `(+) 2 3` 一樣。我們可以從一個不同的角度來看待 `(->) r a`，他其實只是一個接受兩個參數的 type constructor，好比 `Either`。但記住我們說過 `Functor` 只能接受一個 type constructor。這也是為什麼 `(->)` 不是 `Functor` 的一個 instance，但 `(->) r` 則是。如果程式的語法允許的話，你也可以將 `(->) r` 寫成 `(r ->)`。就如 `(2+)` 代表的其實是 `(+) 2`。至於細節是如何呢？我們可以看看 `Control.Monad.Instances`。

```
我們通常說一個接受任何東西以及回傳隨便一個東西的函數型態是 ``a -> b``。``r -> a`` 是同樣意思，只是把符號代換了一下。
```

```haskell
instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))
```

如果語法允許的話，他可以被寫成

```haskell
instance Functor (r ->) where  
    fmap f g = (\x -> f (g x))
```

但其實是不允許的，所以我們必須寫成第一種的樣子。

首先我們來看看 `fmap` 的型態。他的型態是 `fmap :: (a -> b) -> f a -> f b`。我們把所有的 `f` 在心裡代換成 `(->) r`。則 `fmap` 的型態就變成 `fmap :: (a -> b) -> ((->) r a) -> ((->) r b)`。接著我們把 `(->) r a` 跟 `(->) r b` 換成 `r -> a` 跟 `r -> b`。則我們得到 `fmap :: (a -> b) -> (r -> a) -> (r -> b)`。

從上面的結果看到將一個 function map over 一個 function 會得到另一個 function，就如 map over 一個 function 到 `Maybe` 會得到一個 `Maybe`，而 map over 一個 function 到一個 list 會得到一個 list。而 `fmap :: (a -> b) -> (r -> a) -> (r -> b)` 告訴我們什麼？他接受一個從 `a` 到 `b` 的 function，跟一個從 `r` 到 `a` 的 function，並回傳一個從 `r` 到 `b` 的 function。這根本就是 function composition。把 `r -> a` 的輸出接到 `a -> b` 的輸入，的確是 function composition 在做的事。如果你再仔細看看 instance 的定義，會發現真的就是一個 function composition。

```haskell
instance Functor ((->) r) where  
    fmap = (.)
```

這很明顯就是把 `fmap` 當 composition 在用。可以用 `:m + Control.Monad.Instances` 把模組裝載進來，並做一些嘗試。

```haskell
ghci> :t fmap (*3) (+100)  
fmap (*3) (+100) :: (Num a) => a -> a  
ghci> fmap (*3) (+100) 1  
303  
ghci> (*3) `fmap` (+100) $ 1  
303  
ghci> (*3) . (+100) $ 1  
303  
ghci> fmap (show . (*3)) (*100) 1  
"300"
```

我們呼叫 `fmap` 的方式是 infix 的方式，這跟 `.` 很像。在第二行，我們把 `(*3)` map over 到 `(+100)` 上，這會回傳一個先把輸入值 `(+100)` 再 `(*3)` 的 function，我們再用 `1` 去呼叫他。

到這邊為止盒子的比喻還適用嗎？如果你硬是要解釋的話還是解釋得通。當我們將 `fmap (+3)` map over `Just 3` 的時候，對於 `Maybe` 我們很容易把他想成是裝了值的盒子，我們只是對盒子裡面的值 `(+3)`。但對於 `fmap (*3) (+100)` 呢？你可以把 `(+100)` 想成是一個裝了值的盒子。有點像把 I/O action 想成長了腳的盒子一樣。對 `(+100)` 使用 `fmap (*3)` 會產生另一個表現得像 `(+100)` 的 function。只是在算出值之前，會再多計算 `(*3)`。這樣我們可以看出來 `fmap` 表現得就像 `.` 一樣。

`fmap` 等同於 function composition 這件事對我們來說並不是很實用，但至少是一個有趣的觀點。這也讓我們打開視野，看到盒子的比喻不是那麼恰當，functors 其實比較像 computation。function 被 map over 到一個 computation 會產生經由那個 function 映射過後的 computation。

![](/files/-LjUmuY5zXND6PunkRzw)

在我們繼續看 `fmap` 該遵守的規則之前，我們再看一次 `fmap` 的型態，他是 `fmap :: (a -> b) -> f a -> f b`。很明顯我們是在討論 Functor，所以為了簡潔，我們就不寫 `(Functor f) =>` 的部份。當我們在學 curry 的時候，我們說過 Haskell 的 function 實際上只接受一個參數。一個型態是 `a -> b -> c` 的函數實際上是接受 `a` 然後回傳 `b -> c`，而 `b -> c` 實際上接受一個 `b` 然後回傳一個 `c`。如果我們用比較少的參數呼叫一個函數，他就會回傳一個函數需要接受剩下的參數。所以 `a -> b -> c` 可以寫成 `a -> (b -> c)`。這樣 curry 可以明顯一些。

同樣的，我們可以不要把 `fmap` 想成是一個接受 function 跟 functor 並回傳一個 function 的 function。而是想成一個接受 function 並回傳一個新的 function 的 function，回傳的 function 接受一個 functor 並回傳一個 functor。他接受 `a -> b` 並回傳 `f a -> f b`。這動作叫做 lifting。我們用 GHCI 的 `:t` 來做的實驗。

```haskell
ghci> :t fmap (*2)  
fmap (*2) :: (Num a, Functor f) => f a -> f a  
ghci> :t fmap (replicate 3)  
fmap (replicate 3) :: (Functor f) => f a -> f [a]
```

`fmap (*2)` 接受一個 functor `f`，並回傳一個基於數字的 functor。那個 functor 可以是 list，可以是 `Maybe`，可以是 `Either String`。`fmap (replicate 3)` 可以接受一個基於任何型態的 functor，並回傳一個基於 list 的 functor。

```
當我們提到 functor over numbers 的時候，你可以想像他是一個 functor 包含有許多數字在裡面。前面一種說法其實比較正確，但後面一種說法比較容易讓人理解。
```

這樣的觀察在我們只有綁定一個部份套用的函數，像是 `fmap (++"!")`，的時候會顯得更清楚，

你可以把 `fmap` 想做是一個函數，他接受另一個函數跟一個 functor，然後把函數對 functor 每一個元素做映射，或你可以想做他是一個函數，他接受一個函數並把他 lift 到可以在 functors 上面操作。兩種想法都是正確的，而且在 Haskell 中是等價。

`fmap (replicate 3) :: (Functor f) => f a -> f [a]` 這樣的型態代表這個函數可以運作在任何 functor 上。至於確切的行為則要看究竟我們操作的是什麼樣的 functor。如果我們是用 `fmap (replicate 3)` 對一個 list 操作，那我們會選擇 `fmap` 針對 list 的實作，也就是只是一個 `map`。如果我們是碰到 `Maybe a`。那他在碰到 `Just` 型態的時候，會對裡面的值套用 `replicate 3`。而碰到 `Nothing` 的時候就回傳 `Nothing`。

```haskell
ghci> fmap (replicate 3) [1,2,3,4]  
[[1,1,1],[2,2,2],[3,3,3],[4,4,4]]  
ghci> fmap (replicate 3) (Just 4)  
Just [4,4,4]  
ghci> fmap (replicate 3) (Right "blah")  
Right ["blah","blah","blah"]  
ghci> fmap (replicate 3) Nothing  
Nothing  
ghci> fmap (replicate 3) (Left "foo")  
Left "foo"
```

接下來我們來看看 functor laws。一個東西要成為 functor，必須要遵守某些定律。不管任何一個 functor 都被要求具有某些性質。他們必須是能被 map over 的。對他們呼叫 `fmap` 應該是要用一個函數 map 每一個元素，不多做任何事情。這些行為都被 functor laws 所描述。對於 `Functor` 的 instance 來說，總共兩條定律應該被遵守。不過他們不會在 Haskell 中自動被檢查，所以你必須自己確認這些條件。

functor law 的第一條說明，如果我們對 functor 做 map `id`，那得到的新的 functor 應該要跟原來的一樣。如果寫得正式一點，他代表 `fmap id = id`。基本上他就是說對 functor 呼叫 `fmap id`，應該等同於對 functor 呼叫 `id` 一樣。畢竟 `id` 只是 identity function，他只會把參數照原樣丟出。他也可以被寫成 `\x -> x`。如果我們對 functor 的概念就是可以被 map over 的物件，那 `fmap id = id` 的性就顯而易見。

我們來看看這個定律的幾個案例：

```haskell
ghci> fmap id (Just 3)  
Just 3  
ghci> id (Just 3)  
Just 3  
ghci> fmap id [1..5]  
[1,2,3,4,5]  
ghci> id [1..5]  
[1,2,3,4,5]  
ghci> fmap id []  
[]  
ghci> fmap id Nothing  
Nothing
```

如果我們看看 `Maybe` 的 `fmap` 的實作，我們不難發現第一定律為何被遵守。

```haskell
instance Functor Maybe where  
    fmap f (Just x) = Just (f x)  
    fmap f Nothing = Nothing
```

我們可以想像在 `f` 的位置擺上 `id`。我們看到 `fmap id` 拿到 `Just x` 的時候，結果只不過是 `Just (id x)`，而 `id` 有只回傳他拿到的東西，所以可以知道 `Just (id x)` 等價於 `Just x`。所以說我們可以知道對 `Maybe` 中的 `Just` 用 `id` 去做 map over 的動作，會拿回一樣的值。

而將 `id` map over `Nothing` 會拿回 `Nothing` 並不稀奇。所以從這兩個 `fmap` 的實作，我們可以看到的確 `fmap id = id` 有被遵守。

![](/files/-LjUmuY9j8JX7sU0Kti5)

*第二定律描述說先將兩個函數合成並將結果 map over 一個 functor 的結果，應該跟先將第一個函數 map over 一個 functor，再將第二個函數 map over 那個 functor 的結果是一樣的。*&#x6B63;式地寫下來的話就是 `fmap (f . g) = fmap f . fmap g`。或是用另外一種寫法，對於任何一個 functor F，下面這個式子應該要被遵守：`fmap (f . g) F = fmap f (fmap g F)`。

如果我們能夠證明某個型別遵守兩個定律，那我們就可以保證他跟其他 functor 對於映射方面都擁有相同的性質。我們知道如果對他用 `fmap`，我們知道不會有除了 mapping 以外的事會發生，而他就僅僅會表現成某個可以被 map over 的東西。也就是一個 functor。你可以再仔細檢視 `fmap` 對於某些型別的實作來了解第二定律。正如我們先前對 `Maybe` 檢視第一定律一般。

如果你需要的話，我們能在這邊演練一下 `Maybe` 是如何遵守第二定律的。首先 `fmap (f . g)` 來 map over `Nothing` 的話，我們會得到 `Nothing`。因為用任何函數來 `fmap` `Nothing` 的話都會回傳 `Nothing`。如果我們 `fmap f (fmap g Nothing)`，我們會得到 `Nothing`。可以看到當面對 `Nothing` 的時候，`Maybe` 很顯然是遵守第二定律的。 那對於 `Just something` 呢？如果我們使用 `fmap (f . g) (Just x)` 的話，從實作的程式碼中我可以看到 `Just ((f . g ) x)`，也就是 `Just (f (g x))`。如果我們使用 `fmap f (fmap g (Just x))` 的話我們可以從實作知道 `fmap g (Just x)` 會是 `Just (g x)`。`fmap f (fmap g (Just x))` 跟 `fmap f (Just (g x))` 相等。而從實作上這又會相等於 `Just (f (g x))`。

如果你不太理解這邊的說明，別擔心。只要確定你了解什麼是函數合成就好。在多數的情況下你可以直覺地對應到這些型別表現得就像 containers 或函數一樣。或是也可以換種方法，只要多嘗試對型別中不同的值做操作你就可以看看型別是否有遵守定律。

我們來看一些經典的例子。這些型別建構子雖然是 `Functor` 的 instance，但實際上他們並不是 functor，因為他們並不遵守這些定律。我們來看看其中一個型別。

```haskell
data CMaybe a = CNothing | CJust Int a deriving (Show)
```

C 這邊代表的是計數器。他是一種看起來像是 `Maybe a` 的型別，只差在 `Just` 包含了兩個 field 而不是一個。在 `CJust` 中的第一個 field 是 `Int`，他是扮演計數器用的。而第二個 field 則為型別 `a`，他是從型別參數來的，而他確切的型別當然會依據我們選定的 `CMaybe a` 而定。我們來對他作些操作來獲得些操作上的直覺吧。

```haskell
ghci> CNothing  
CNothing  
ghci> CJust 0 "haha"  
CJust 0 "haha"  
ghci> :t CNothing  
CNothing :: CMaybe a  
ghci> :t CJust 0 "haha"  
CJust 0 "haha" :: CMaybe [Char]  
ghci> CJust 100 [1,2,3]  
CJust 100 [1,2,3]
```

如果我們使用 `CNothing`，就代表不含有 field。如果我們用的是 `CJust`，那第一個 field 是整數，而第二個 field 可以為任何型別。我們來定義一個 `Functor` 的 instance，這樣每次我們使用 `fmap` 的時候，函數會被套用在第二個 field，而第一個 field 會被加一。

```haskell
instance Functor CMaybe where  
    fmap f CNothing = CNothing  
    fmap f (CJust counter x) = CJust (counter+1) (f x)
```

這種定義方式有點像是 `Maybe` 的定義方式，只差在當我們使用 `fmap` 的時候，如果碰到的不是空值，那我們不只會套用函數，還會把計數器加一。我們可以來看一些範例操作。

```haskell
ghci> fmap (++"ha") (CJust 0 "ho")  
CJust 1 "hoha"  
ghci> fmap (++"he") (fmap (++"ha") (CJust 0 "ho"))  
CJust 2 "hohahe"  
ghci> fmap (++"blah") CNothing  
CNothing
```

這些會遵守 functor laws 嗎？要知道有不遵守的情形，只要找到一個反例就好了。

```haskell
ghci> fmap id (CJust 0 "haha")  
CJust 1 "haha"  
ghci> id (CJust 0 "haha")  
CJust 0 "haha"
```

我們知道 functor law 的第一定律描述當我們用 `id` 來 map over 一個 functor 的時候，他的結果應該跟只對 functor 呼叫 `id` 的結果一樣。但我們可以看到這個例子中，這對於 `CMaybe` 並不遵守。儘管他的確是 `Functor` typeclass 的一個 instace。但他並不遵守 functor law 因此不是一個 functor。如果有人使用我們的 `CMaybe` 型別，把他當作 functor 用，那他就會期待 functor laws 會被遵守。但 `CMaybe` 並沒辦法滿足，便會造成錯誤的程式。當我們使用一個 functor 的時候，函數合成跟 map over 的先後順序不應該有影響。但對於 `CMaybe` 他是有影響的，因為他紀錄了被 map over 的次數。如果我們希望 `CMaybe` 遵守 functor law，我們必須要讓 `Int` 欄位在做 `fmap` 的時候維持不變。

乍看之下 functor laws 看起來不是很必要，也容易讓人搞不懂，但我們知道如果一個型別遵守 functor laws，那我們就能對他作些基本的假設。如果遵守了 functor laws，我們知道對他做 `fmap` 不會做多餘的事情，只是用一個函數做映射而已。這讓寫出來的程式碼足夠抽象也容易擴展。因為我們可以用定律來推論型別的行為。

所有在標準函式庫中的 `Functor` 的 instance 都遵守這些定律，但你可以自己檢查一遍。下一次你定義一個型別為 `Functor` 的 instance 的時候，花點時間確認他確實遵守 functor laws。一旦你操作過足夠多的 functors 時，你就會獲得直覺，知道他們會有什麼樣的性質跟行為。而且 functor laws 也會覺得顯而易見。但就算沒有這些直覺，你仍然可以一行一行地來找看看有沒有反例讓這些定律失效。

我們可以把 functor 看作輸出具有 context 的值。例如說 `Just 3` 就是輸出 `3`，但他又帶有一個可能沒有值的 context。`[1,2,3]` 輸出三個值，`1`,`2` 跟 `3`，同時也帶有可能有多個值或沒有值的 context。`(+3)` 則會帶有一個依賴於參數的 context。

如果你把 functor 想做是輸出值這件事，那你可以把 map over 一個 functor 這件事想成在 functor 輸出的後面再多加一層轉換。當我們做 `fmap (+3) [1,2,3]` 的時候，我們是把 `(+3)` 接到 `[1,2,3]` 後面，所以當我們檢視任何一個 list 的輸出的時候，`(+3)` 也會被套用在上面。另一個例子是對函數做 map over。當我們做 `fmap (+3) (*3)`，我們是把 `(+3)` 這個轉換套用在 `(*3)` 後面。這樣想的話會很自然就會把 `fmap` 跟函數合成關聯起來（`fmap (+3) (*3)` 等價於 `(+3) . (*3)`，也等價於 `\x -> ((x*3)+3)`），畢竟我們是接受一個函數 `(*3)` 然後套用 `(+3)` 轉換。最後的結果仍然是一個函數，只是當我們餵給他一個數字的時候，他會先乘上三然後做轉換加上三。這基本上就是函數合成在做的事。

## Applicative functors

![](/files/-LjUmuYB13LxbRcukGRN)

在這個章節中，我們會學到 applicative functors，也就是加強版的 functors，在 Haskell 中是用在 `Control.Applicative` 中的 `Applicative` 這個 typeclass 來定義的。

你還記得 Haskell 中函數預設就是 Curried 的，那代表接受多個參數的函數實際上是接受一個參數然後回傳一個接受剩餘參數的函數，以此類推。如果一個函數的型別是 `a -> b -> c`，我們通常會說這個函數接受兩個參數並回傳 `c`，但他實際上是接受 `a` 並回傳一個 `b -> c` 的函數。這也是為什麼我們可以用 `(f x) y` 的方式呼叫 `f x y`。這個機制讓我們可以 partially apply 一個函數，可以用比較少的參數呼叫他們。可以做成一個函數再餵給其他函數。

到目前為止，當我們要對 functor map over 一個函數的時候，我們用的函數都是只接受一個參數的。但如果我們要 map 一個接受兩個參數的函數呢？我們來看幾個具體的例子。如果我們有 `Just 3` 然後我們做 `fmap (*) (Just 3)`，那我們會獲得什麼樣的結果？從 `Maybe` 對 `Functor` 的 instance 實作來看，我們知道如果他是 `Just something`，他會對在 `Just` 中的 `something` 做映射。因此當 `fmap (*) (Just 3)` 會得到 `Just ((*) 3)`，也可以寫做 `Just (* 3)`。我們得到了一個包在 `Just` 中的函數。

```haskell
ghci> :t fmap (++) (Just "hey")  
fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])  
ghci> :t fmap compare (Just 'a')  
fmap compare (Just 'a') :: Maybe (Char -> Ordering)  
ghci> :t fmap compare "A LIST OF CHARS"  
fmap compare "A LIST OF CHARS" :: [Char -> Ordering]  
ghci> :t fmap (\x y z -> x + y / z) [3,4,5,6]  
fmap (\x y z -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]
```

如果我們 map `compare` 到一個包含許多字元的 list 呢？他的型別是 `(Ord a) => a -> a -> Ordering`，我們會得到包含許多 `Char -> Ordering` 型別函數的 list，因為 `compare` 被 partially apply 到 list 中的字元。他不是包含許多 `(Ord a) => a -> Ordering` 的函數，因為第一個 `a` 碰到的型別是 `Char`，所以第二個 `a` 也必須是 `Char`。

我們看到如何用一個多參數的函數來 map functor，我們會得到一個包含了函數的 functor。那現在我們能對這個包含了函數的 functor 做什麼呢？我們能用一個吃這些函數的函數來 map over 這個 functor，這些在 functor 中的函數都會被當作參數丟給我們的函數。

```haskell
ghci> let a = fmap (*) [1,2,3,4]  
ghci> :t a  
a :: [Integer -> Integer]  
ghci> fmap (\f -> f 9) a  
[9,18,27,36]
```

但如果我們的有一個 functor 裡面是 `Just (3 *)` 還有另一個 functor 裡面是 `Just 5`，但我們想要把第一個 `Just (3 *)` map over `Just 5` 呢？如果是普通的 functor，那就沒救了。因為他們只允許 map 一個普通的函數。即使我們用 `\f -> f 9` 來 map 一個裝了很多函數的 functor，我們也是使用了普通的函數。我們是無法單純用 `fmap` 來把包在一個 functor 的函數 map 另一個包在 functor 中的值。我們能用模式匹配 `Just` 來把函數從裡面抽出來，然後再 map `Just 5`，但我們是希望有一個一般化的作法，對任何 functor 都有效。

我們來看看 `Applicative` 這個 typeclass。他位在 `Control.Applicative` 中，在其中定義了兩個函數 `pure` 跟 `<*>`。他並沒有提供預設的實作，如果我們想使用他必須要為他們 applicative functor 的實作。typeclass 定義如下：

```haskell
class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b
```

這簡簡單單的三行可以讓我們學到不少。首先來看第一行。他開啟了 `Applicative` 的定義，並加上 class contraint。描述了一個型別構造子要是 `Applicative`，他必須也是 `Functor`。這就是為什麼我們說一個型別構造子屬於 `Applicative` 的話，他也會是 `Functor`，因此我們能對他使用 `fmap`。

第一個定義的是 `pure`。他的型別宣告是 `pure :: a -> f a`。`f` 代表 applicative functor 的 instance。由於 Haskell 有一個優秀的型別系統，其中函數又是將一些參數映射成結果，我們可以從型別宣告中讀出許多訊息。`pure` 應該要接受一個值，然後回傳一個包含那個值的 applicative functor。我們這邊是用盒子來作比喻，即使有一些比喻不完全符合現實的情況。儘管這樣，`a -> f a` 仍有許多豐富的資訊，他確實告訴我們他會接受一個值並回傳一個 applicative functor，裡面裝有結果。

對於 `pure` 比較好的說法是把一個普通值放到一個預設的 context 下，一個最小的 context 但仍然包含這個值。

`<*>` 也非常有趣。他的型別是 `f (a -> b) -> f a -> f b`。這有讓你聯想到什麼嗎？沒錯！就是 `fmap :: (a -> b) -> f a -> f b`。他有點像加強版的 `fmap`。然而 `fmap` 接受一個函數跟一個 functor，然後套用 functor 之中的函數。`<*>` 則是接受一個裝有函數的 functor 跟另一個 functor，然後取出第一個 functor 中的函數將他對第二個 functor 中的值做 map。

我們來看看 `Maybe` 的 `Applicative` 實作：

```haskell
instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something
```

從 class 的定義我們可以看到 `f` 作為 applicative functor 會接受一個具體型別當作參數，所以我們是寫成 `instance Applicative Maybe where` 而不是寫成 `instance Applicative (Maybe a) where`。

首先看到 `pure`。他只不過是接受一個東西然後包成 applicative functor。我們寫成 `pure = Just` 是因為 `Just` 不過就是一個普通函數。我們其實也可以寫成 `pure x = Just x`。

接著我們定義了 `<*>`。我們無法從 `Nothing` 中抽出一個函數，因為 `Nothing` 並不包含一個函數。所以我們說如果我們要嘗試從 `Nothing` 中取出一個函數，結果必定是 `Nothing`。如果你看看 `Applicative` 的定義，你會看到他有 `Functor` 的限制，他代表 `<*>` 的兩個參數都會是 functors。如果第一個參數不是 `Nothing`，而是一個裝了函數的 `Just`，而且我們希望將這個函數對第二個參數做 map。這個也考慮到第二個參數是 `Nothing` 的情況，因為 `fmap` 任何一個函數至 `Nothing` 會回傳 `Nothing`。

對於 `Maybe` 而言，如果左邊是 `Just`，那 `<*>` 會從其中抽出了一個函數來 map 右邊的值。如果有任何一個參數是 `Nothing`。那結果便是 `Nothing`。

來試試看吧！

```haskell
ghci> Just (+3) <*> Just 9  
Just 12  
ghci> pure (+3) <*> Just 10  
Just 13  
ghci> pure (+3) <*> Just 9  
Just 12  
ghci> Just (++"hahah") <*> Nothing  
Nothing  
ghci> Nothing <*> Just "woot"  
Nothing
```

我們看到 `pure (+3)` 跟 `Just (+3)` 在這個 case 下是一樣的。如果你是在 applicative context 底下跟 `Maybe` 打交道的話請用 `pure`，要不然就用 `Just`。前四個輸入展示了函數是如何被取出並做 map 的動作，但在這個 case 底下，他們同樣也可以用 unwrap 函數來 map over functors。最後一行比較有趣，因為我們試著從 `Nothing` 取出函數並將他 map 到某個值。結果當然是 `Nothing`。

對於普通的 functors，你可以用一個函數 map over 一個 functors，但你可能沒辦法拿到結果。而 applicative functors 則讓你可以用單一一個函數操作好幾個 functors。看看下面一段程式碼：

```haskell
ghci> pure (+) <*> Just 3 <*> Just 5  
Just 8  
ghci> pure (+) <*> Just 3 <*> Nothing  
Nothing  
ghci> pure (+) <*> Nothing <*> Just 5  
Nothing
```

![](/files/-LjUmuYE_NA2JQlhO1TT)

究竟我們寫了些什麽？我們來一步步看一下。`<*>` 是 left-associative，也就是說 `pure (+) <*> Just 3 <*> Just 5` 可以寫成 `(pure (+) <*> Just 3) <*> Just 5`。首先 `+` 是擺在一個 functor 中，在這邊剛好他是一個 `Maybe`。所以首先，我們有 `pure (+)`，他等價於 `Just (+)`。接下來由於 partial application 的關係，`Just (+) <*> Just 3` 等價於 `Just (3+)`。把一個 `3` 餵給 `+` 形成另一個只接受一個參數的函數，他的效果等於加上 3。最後 `Just (3+) <*> Just 5` 被運算，其結果是 `Just 8`。

這樣很棒吧！用 applicative style 的方式來使用 applicative functors。像是 `pure f <*> x <*> y <*> ...` 就讓我們可以拿一個接受多個參數的函數，而且這些參數不一定是被包在 functor 中。就這樣來套用在多個在 functor context 的值。這個函數可以吃任意多的參數，畢竟 `<*>` 只是做 partial application 而已。

如果我們考慮到 `pure f <*> x` 等於 `fmap f x` 的話，這樣的用法就更方便了。這是 applicative laws 的其中一條。我們稍後會更仔細地檢視這條定律。現在我們先依直覺來使用他。就像我們先前所說的，`pure` 把一個值放進一個預設的 context 中。如果我們要把一個函數放在一個預設的 context，然後把他取出並套用在放在另一個 applicative functor 的值。我們會做的事就是把函數 map over 那個 applicative functor。但我們不會寫成 `pure f <*> x <*> y <*> ...`，而是寫成 `fmap f x <*> y <*> ...`。這也是為什麽 `Control.Applicative` 會 export 一個函數 `<$>`，他基本上就是中綴版的 `fmap`。他是這麼被定義的：

```haskell
(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x
```

```
要記住型別變數跟參數的名字還有值綁定的名稱不衝突。``f`` 在函數的型別宣告中是型別變數，說明 ``f`` 應該要滿足 ``Functor`` typeclass 的條件。而在函數本體中的 ``f`` 則表示一個函數，我們將他 map over x。我們同樣用 ``f`` 來表示他們並代表他們是相同的東西。
```

`<$>` 的使用顯示了 applicative style 的好處。如果我們想要將 `f` 套用三個 applicative functor。我們可以寫成 `f <$> x <*> y <*> z`。如果參數不是 applicative functor 而是普通值的話。我們則寫成 `f x y z`。

我們再仔細看看他是如何運作的。我們有一個 `Just "johntra"` 跟 `Just "volta"` 這樣的值，我們希望將他們結合成一個 `String`，並且包含在 `Maybe` 中。我們會這樣做：

```haskell
ghci> (++) <$> Just "johntra" <*> Just "volta"  
Just "johntravolta"
```

可以將上面的跟下面這行比較一下：

```haskell
ghci> (++) "johntra" "volta"  
"johntravolta"
```

可以將一個普通的函數套用在 applicative functor 上真不錯。只要稍微寫一些 `<$>` 跟 `<*>` 就可以把函數變成 applicative style，可以操作 applicatives 並回傳 applicatives。

總之當我們在做 `(++) <$> Just "johntra" <*> Just "volta"` 時，首先我們將 `(++)` map over 到 `Just "johntra"`，然後產生 `Just ("johntra"++)`，其中 `(++)` 的型別為 `(++) :: [a] -> [a] -> [a]`，`Just ("johntra"++)` 的型別為 `Maybe ([Char] -> [Char])`。注意到 `(++)` 是如何吃掉第一個參數，以及我們是怎麼決定 `a` 是 `Char` 的。當我們做 `Just ("johntra"++) <*> Just "volta"`，他接受一個包在 `Just` 中的函數，然後 map over `Just "volta"`，產生了 `Just "johntravolta"`。如果兩個值中有任意一個為 `Nothing`，那整個結果就會是 `Nothing`。

到目前為止我們只有用 `Maybe` 當作我們的案例，你可能也會想說 applicative functor 差不多就等於 `Maybe`。不過其實有許多其他 `Applicative` 的 instance。我們來看看有哪些。

List 也是 applicative functor。很驚訝嗎？來看看我們是怎麼定義 `[]` 為 `Applicative` 的 instance 的。

```haskell
instance Applicative [] where  
    pure x = [x]  
    fs <*> xs = [f x | f <- fs, x <- xs]
```

早先我們說過 `pure` 是把一個值放進預設的 context 中。換種說法就是一個會產生那個值的最小 context。而對 list 而言最小 context 就是 `[]`，但由於空的 list 並不包含一個值，所以我們沒辦法把他當作 `pure`。這也是為什麼 `pure` 其實是接受一個值然後回傳一個包含單元素的 list。同樣的，`Maybe` 的最小 context 是 `Nothing`，但他其實表示的是沒有值。所以 `pure` 其實是被實作成 `Just` 的。

```haskell
ghci> pure "Hey" :: [String]  
["Hey"]  
ghci> pure "Hey" :: Maybe String  
Just "Hey"
```

至於 `<*>` 呢？如果我們假定 `<*>` 的型別是限制在 list 上的話，我們會得到 `(<*>) :: [a -> b] -> [a] -> [b]`。他是用 list comprehension 來實作的。`<*>` 必須要從左邊的參數取出函數，將他 map over 右邊的參數。但左邊的 list 有可能不包含任何函數，也可能包含一個函數，甚至是多個函數。而右邊的 list 有可能包含多個值。這也是為什麼我們用 list comprehension 的方式來從兩個 list 取值。我們要對左右任意的組合都做套用的動作。而得到的結果就會是左右兩者任意組合的結果。

```haskell
ghci> [(*0),(+100),(^2)] <*> [1,2,3]  
[0,0,0,101,102,103,1,4,9]
```

左邊的 list 包含三個函數，而右邊的 list 有三個值。所以結果會是有九個元素的 list。在左邊 list 中的每一個函數都被套用到右邊的值。如果我們今天在 list 中的函數是接收兩個參數的，我們也可以套用到兩個 list 上。

```haskell
ghci> [(+),(*)] <*> [1,2] <*> [3,4]  
[4,5,5,6,3,4,6,8]
```

由於 `<*>` 是 left-associative，也就是說 `[(+),(*)] <*> [1,2]` 會先運作，產生 `[(1+),(2+),(1*),(2*)]`。由於左邊的每一個函數都套用至右邊的每一個值。也就產生 `[(1+),(2+),(1*),(2*)] <*> [3,4]`，其便是最終結果。

list 的 applicative style 是相當有趣的：

```haskell
ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."]  
["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."]
```

看看我們是如何將一個接受兩個字串參數的函數套用到兩個 applicative functor 上的，只要用適當的 applicative 運算子就可以達成。

你可以將 list 看作是一個 non-deterministic 的計算。而對於像 `100` 或是 `"what"` 這樣的值則是 deterministic 的計算，只會有一個結果。而 `[1,2,3]` 則可以看作是沒有確定究竟是哪一種結果。所以他代表的是所有可能的結果。當你在做 `(+) <$> [1,2,3] <*> [4,5,6]`，你可以想做是把兩個 non-deterministic 的計算做 `+`，只是他會產生另一個 non-deterministic 的計算，而且結果更加不確定。

Applicative style 對於 list 而言是一個取代 list comprehension 的好方式。在第二章中，我們想要看到 `[2,5,10]` 跟 `[8,10,11]` 相乘的結果，所以我們這樣做：

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

我們只是從兩個 list 中取出元素，並將一個函數套用在任何元素的組合上。這也可以用 applicative style 的方式來寫：

```haskell
ghci> (*) <$> [2,5,10] <*> [8,10,11]  
[16,20,22,40,50,55,80,100,110]
```

這寫法對我來說比較清楚。可以清楚表達我們是要對兩個 non-deterministic 的計算做 `*`。如果我們想要所有相乘大於 50 可能的計算結果，我們會這樣寫：

```haskell
ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]  
[55,80,100,110]
```

很容易看到 `pure f <*> xs` 等價於 `fmap f xs`。而 `pure f` 就是 `[f]`，而且 `[f] <*> xs` 可將左邊的每個函數套用至右邊的每個值。但左邊其實只有一個函數，所以他做起來就像是 mapping。

另一個我們已經看過的 `Applicative` 的 instance 是 `IO`，來看看他是怎麼實作的：

```haskell
instance Applicative IO where  
    pure = return  
    a <*> b = do  
        f <- a  
        x <- b  
        return (f x)
```

![](/files/-LjUmuYHKJRWT-cdHB5p)

由於 `pure` 是把一個值放進最小的 context 中，所以將 `return` 定義成 `pure` 是很合理的。因為 `return` 也是做同樣的事情。他做了一個不做任何事情的 I/O action，他可以產生某些值來作為結果，但他實際上並沒有做任何 I/O 的動作，例如說印出結果到終端或是檔案。

如果 `<*>` 被限定在 `IO` 上操作的話，他的型別會是 `(<*>) :: IO (a -> b) -> IO a -> IO b`。他接受一個產生函數的 I/O action，還有另一個 I/O action，並從以上兩者創造一個新的 I/O action，也就是把第二個參數餵給第一個參數。而得到回傳的結果，然後放到新的 I/O action 中。我們用 do 的語法來實作他。你還記得的話 do 就是把好幾個 I/O action 黏在一起，變成一個大的 I/O action。

而對於 `Maybe` 跟 `[]` 而言，我們可以把 `<*>` 想做是從左邊的參數取出一個函數，然後套用到右邊的參數上。至於 `IO`，這種取出的類比方式仍然適用，但我們必須多加一個 sequencing 的概念，因為我們是從兩個 I/O action 中取值，也是在 sequencing，把他們黏成一個。我們從第一個 I/O action 中取值，但要取出 I/O action 的結果，他必須要先被執行過。

考慮下面這個範例：

```haskell
myAction :: IO String  
myAction = do  
    a <- getLine  
    b <- getLine  
    return $ a ++ b
```

這是一個提示使用者輸入兩行並產生將兩行輸入串接在一起結果的一個 I/O action。我們先把兩個 `getLine` 黏在一起，然後用一個 `return`，這是因為我們想要這個黏成的 I/O action 包含 `a ++ b` 的結果。我們也可以用 applicative style 的方式來描述：

```haskell
myAction :: IO String  
myAction = (++) <$> getLine <*> getLine
```

我們先前的作法是將兩個 I/O action 的結果餵給函數。還記得 `getLine` 的型別是 `getLine :: IO String`。當我們對 applicative functor 使用 `<*>` 的時候，結果也會是 applicative functor。

如果我們再使用盒子的類比，我們可以把 `getLine` 想做是一個去真實世界中拿取字串的盒子。而 `(++) <$> getLine <*> getLine` 會創造一個比較大的盒子，這個大盒子會派兩個盒子去終端拿取字串，並把結果串接起來放進自己的盒子中。

`(++) <$> getLine <*> getLine` 的型別是 `IO String`，他代表這個表達式式一個再普通不過的 I/O action，他裡面也裝著某種值。這也是為什麼我們可以這樣寫：

```haskell
main = do  
    a <- (++) <$> getLine <*> getLine  
    putStrLn $ "The two lines concatenated turn out to be: " ++ a
```

如果你發現你是在做 binding I/O action 的動作，而且在 binding 之後還呼叫一些函數，最後用 `return` 來將結果包起來。 那你可以考慮使用 applicative style，這樣可以更簡潔。

另一個 `Applicative` 的 instance 是 `(->) r`。雖然他們通常是用在 code golf 的情況，但他們還是十分有趣的例子。所以我們還是來看一下他們是怎麼被實作的。

```
如果你忘記 ``(->) r`` 的意思，回去翻翻前一章節我們介紹 ``(->) r`` 作為一個 functor 的範例。
```

```haskell
instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x)
```

當我們用 `pure` 將一個值包成 applicative functor 的時候，他產生的結果永遠都會是那個值。也就是最小的 context。那也是為什麼對於 function 的 `pure` 實作來講，他就是接受一個值，然後造一個函數永遠回傳那個值，不管他被餵了什麼參數。如果你限定 `pure` 的型別至 `(->) r` 上，他就會是 `pure :: a -> (r -> a)`。

```haskell
ghci> (pure 3) "blah"  
3
```

由於 currying 的關係，函數套用是 left-associative，所以我們忽略掉括弧。

```haskell
ghci> pure 3 "blah"  
3
```

而 `<*>` 的實作是比較不容易瞭解的，我們最好看一下怎麼用 applicative style 的方式來使用作為 applicative functor 的 function。

```haskell
ghci> :t (+) <$> (+3) <*> (*100)  
(+) <$> (+3) <*> (*100) :: (Num a) => a -> a  
ghci> (+) <$> (+3) <*> (*100) $ 5  
508
```

將兩個 applicative functor 餵給 `<*>` 可以產生一個新的 applicative functor，所以如果我們丟給他兩個函數，我們能得到一個新的函數。所以是怎麼一回事呢？當我們做 `(+) <$> (+3) <*> (*100)`，我們是在實作一個函數，他會將 `(+3)` 跟 `(*100)` 的結果再套用 `+`。要看一個實際的範例的話，可以看一下 `(+) <$> (+3) <*> (*100) $ 5` 首先 `5` 被丟給 `(+3)` 跟 `(*100)`，產生 `8` 跟 `500`。然後 `+` 被套用到 `8` 跟 `500`，得到 `508`。

```haskell
ghci> (\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5  
[8.0,10.0,2.5]
```

![](/files/-LjUmuYKJtOn5RM_kwCQ)

這邊也一樣。我們創建了一個函數，他會呼叫 `\x y z -> [x,y,z]`，而丟的參數是 `(+3)`, `(*2)` 跟 `(/2)`。`5` 被丟給以上三個函數，然後他們結果又接到 `\x y z -> [x, y, z]`。

你可以將函數想做是裝著最終結果的盒子，所以 `k <$> f <*> g` 會製造一個函數，他會將 `f` 跟 `g` 的結果丟給 `k`。當我們做 `(+) <$> Just 3 <*> Just 5`，我們是用 `+` 套用在一些可能有或可能沒有的值上，所以結果也會是可能有或沒有。當我們做 `(+) <$> (+10) <*> (+5)`，我們是將 `+` 套用在 `(+10)` 跟 `(+5)` 的結果上，而結果也會是一個函數，當被餵給一個參數的時候會產生結果。

我們通常不會將函數當作 applicative 用，不過仍然值得當作練習。對於 `(->) r` 怎麼定義成 `Applicative` 的並不是真的那麼重要，所以如果你不是很懂的話也沒關係。這只是讓你獲得一些操作上的直覺罷了。

一個我們之前還沒碰過的 `Applicative` 的 instance 是 `ZipList`，他是包含在 `Control.Applicative` 中。

對於 list 要作為一個 applicative functor 可以有多種方式。我們已經介紹過其中一種。如果套用 `<*>`，左邊是許多函數，而右邊是許多值，那結果會是函數套用到值的所有組合。如果我們做 `[(+3),(*2)] <*> [1,2]`。那 `(+3)` 會先套用至 `1` 跟 `2`。接著 `(*2)` 套用至 `1` 跟 `2`。而得到 `[4,5,2,4]`。

然而 `[(+3),(*2)] <*> [1,2]` 也可以這樣運作:把左邊第一個函數套用至右邊第一個值，接著左邊第二個函數套用右邊第二個值，以此類推。這樣得到的會是 `[4,4]`。或是 `[1 + 3, 2 * 2]`。

由於一個型別不能對同一個 typeclass 定義兩個 instance，所以才會定義了 `ZipList a`，他只有一個構造子 `ZipList`，他只包含一個欄位，他的型別是 list。

```haskell
instance Applicative ZipList where  
        pure x = ZipList (repeat x)  
        ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)
```

`<*>` 做的就是我們之前說的。他將第一個函數套用至第一個值，第二個函數套用第二個值。這也是 `zipWith (\f x -> f x) fs xs` 做的事。由於 `zipWith` 的特性，所以結果會跟 list 中比較短的那個一樣長。

`pure` 也值得我們討論一下。他接受一個值，把他重複地放進一個 list 中。`pure "haha"` 就會是 `ZipList (["haha","haha","haha"...`。這可能會造成些混淆，畢竟我們說過 `pure` 是把一個值放進一個最小的 context 中。而你會想說無限長的 list 不可能會是一個最小的 context。但對於 zip list 來說這是很合理的，因為他必須在 list 的每個位置都有值。這也遵守了 `pure f <*> xs` 必須要等價於 `fmap f xs` 的特性。如果 `pure 3` 只是回傳 `ZipList [3]`，那 `pure (*2) <*> ZipList [1,5,10]` 就只會算出 `ZipList [2]`，因為兩個 zip list 算出結果的長度會是比較短的那個的長度。如果我們 zip 一個有限長的 list 以及一個無限長的 list，那結果的長會是有限長的 list 的長度。

那 zip list 是怎麼用 applicative style 操作的呢？我們來看看，`ZipList a` 型別並沒有定義成 `Show` 的 instance，所以我們必須用 `getZipList` 函數來從 zip list 取出一個普通的 list。

```haskell
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]  
[101,102,103]  
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..]  
[101,102,103]  
ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]  
[5,3,3,4]  
ghci> getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat"  
[('d','c','r'),('o','a','a'),('g','t','t')]
```

```
``(,,)`` 函數跟 ``\x y z -> (x,y,z)`` 是等價的，而 ``(,)`` 跟 ``\x y -> (x,y)`` 是等價的。
```

除了 `zipWith`，標準函式庫中也有 `zipWith3`, `zipWith4` 之類的函數，最多支援到 7。`zipWith` 接受一個接受兩個參數的函數，並把兩個 list zip 起來。`zipWith3` 則接受一個接受三個參數的函數，然後把三個 list zip 起來。以此類推。用 applicative style 的方式來操作 zip list 的話，我們就不需要對每個數量的 list 都定義一個獨立的 zip 函數來 zip 他們。我們只需要用 applicative style 的方式來把任意數量的 list zip 起來就可以了。

`Control.Applicative` 定義了一個函數叫做 `liftA2`，他的型別是 `liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c`。他定義如下：

```haskell
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c  
liftA2 f a b = f <$> a <*> b
```

並沒有太難理解的東西，他不過就是對兩個 applicatives 套用函數而已，而不用我們剛剛熟悉的 applicative style。我們提及他的理由只是要展示為什麼 applicative functors 比起一般的普通 functor 要強。如果只是普通的 functor 的話，我們只能將一個函數 map over 這個 functor。但有了 applicative functor，我們可以對好多個 functor 套用一個函數。看看這個函數的型別，他會是 `(a -> b -> c) -> (f a -> f b -> f c)`。當我們從這樣的角度來看他的話，我們可以說 `liftA2` 接受一個普通的二元函數，並將他升級成一個函數可以運作在兩個 functor 之上。

另外一個有趣的概念是，我們可以接受兩個 applicative functor 並把他們結合成一個 applicative functor，這個新的將這兩個 applicative functor 裝在 list 中。舉例來說，我們現在有 `Just 3` 跟 `Just 4`。我們假設後者是一個只包含單元素的 list。

```haskell
ghci> fmap (\x -> [x]) (Just 4)  
Just [4]
```

所以假設我們有 `Just 3` 跟 `Just [4]`。我們有怎麼得到 `Just [3,4]` 呢？很簡單。

```haskell
ghci> liftA2 (:) (Just 3) (Just [4])  
Just [3,4]  
ghci> (:) <$> Just 3 <*> Just [4]  
Just [3,4]
```

還記得 `:` 是一個函數，他接受一個元素跟一個 list，並回傳一個新的 list，其中那個元素已經接在前面。現在我們有了 `Just [3,4]`，我們能夠將他跟 `Just 2` 綁在一起變成 `Just [2,3,4]` 嗎？當然可以。我們可以將任意數量的 applicative 綁在一起變成一個 applicative，裡面包含一個裝有結果的 list。我們試著實作一個函數，他接受一串裝有 applicative 的 list，然後回傳一個 applicative 裡面有一個裝有結果的 list。我們稱呼他為 `sequenceA`。

```haskell
sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
```

居然用到了遞迴！首先我們來看一下他的型別。他將一串 applicative 的 list 轉換成一個 applicative 裝有一個 list。從這個資訊我們可以推測出邊界條件。如果我們要將一個空的 list 變成一個裝有 list 的 applicative。我們只要把這個空的 list 放進一個預設的 context。現在來看一下我們怎麼用遞迴的。如果們有一個可以分成頭跟尾的 list（`x` 是一個 applicative 而 `xs` 是一串 applicatve），我們可以對尾巴呼叫 `sequenceA`，便會得到一個裝有 list 的 applicative。然後我們只要將在 `x` 中的值把他接到裝有 list 的 applicative 前面就可以了。

所以如果我們做 `sequenceA [Just 1, Just 2]`，也就是 `(:) <$> Just 1 <*> sequenceA [Just 2]`。那會等價於 `(:) <$> Just 1 <*> ((:) <$> Just 2 <*> sequenceA [])`。我們知道 `sequenceA []` 算出來會是 `Just []`，所以運算式就變成 `(:) <$> Just 1 <*> ((:) <$> Just 2 <*> Just [])`，也就是 `(:) <$> Just 1 <*> Just [2]`，算出來就是 `Just [1,2]`。

另一種實作 `sequenceA` 的方式是用 fold。要記得幾乎任何需要走遍整個 list 並 accumulate 成一個結果的都可以用 fold 來實作。

```haskell
sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA = foldr (liftA2 (:)) (pure [])
```

我們從右往左走，並且起始的 accumulator 是用 `pure []`。我們是用 `liftA2 (:)` 來結合 accumulator 跟 list 中最後的元素，而得到一個 applicative，裡面裝有一個單一元素的一個 list。然後我們再用 `liftA2 (:)` 來結合 accumulator 跟最後一個元素，直到我們只剩下 accumulator 為止，而得到一個 applicative，裡面裝有所有結果。

我們來試試看套用在不同 applicative 上。

```haskell
ghci> sequenceA [Just 3, Just 2, Just 1]  
Just [3,2,1]  
ghci> sequenceA [Just 3, Nothing, Just 1]  
Nothing  
ghci> sequenceA [(+3),(+2),(+1)] 3  
[6,5,4]  
ghci> sequenceA [[1,2,3],[4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> sequenceA [[1,2,3],[4,5,6],[3,4,4],[]]  
[]
```

很酷吧。當我們套用在 `Maybe` 上時，`sequenceA` 創造一個新的 `Maybe`，他包含了一個 list 裝有所有結果。如果其中一個值是 `Nothing`，那整個結果就會是 `Nothing`。如果你有一串 `Maybe` 型別的值，但你只在乎當結果不包含任何 `Nothing` 的情況，這樣的特性就很方便。

當套用在函數時，`sequenceA` 接受裝有一堆函數的 list，並回傳一個回傳 list 的函數。在我們的範例中，我們寫了一個函數，他只接受一個數值作為參數，他會把他套用至 list 中的每一個函數，並回傳一個包含結果的 list。`sequenceA [(+3),(+2),(+1)] 3` 會將 `3` 餵給 `(+3)`, `(+2)` 跟 `(+1)`，然後將所有結果裝在一個 list 中。

而 `(+) <$> (+3) <*> (*2)` 會創見一個接受單一參數的一函數，將他同時餵給 `(+3)` 跟 `(*2)`，然後呼叫 `+` 來將兩者加起來。同樣的道理，`sequenceA [(+3),(*2)]` 是製造一個接受單一參數的函數，他會將他餵給所有包含在 list 中的函數。但他最後不是呼叫 `+`，而是呼叫 `:` 跟 `pure []` 來把結果接成一個 list，得到最後的結果。

當我們有一串函數，我們想要將相同的輸入都餵給他們並檢視結果的時候，`sequenceA` 非常好用。例如說，我們手上有一個數值，但不知道他是否滿足一串 predicate。一種實作的方式是像這樣：

```haskell
ghci> map (\f -> f 7) [(>4),(<10),odd]  
[True,True,True]  
ghci> and $ map (\f -> f 7) [(>4),(<10),odd]  
True
```

記住 `and` 接受一串布林值，並只有在全部都是 `True` 的時候才回傳 `True`。 另一種實作方式是用 `sequenceA`：

```haskell
ghci> sequenceA [(>4),(<10),odd] 7  
[True,True,True]  
ghci> and $ sequenceA [(>4),(<10),odd] 7  
True
```

`sequenceA [(>4),(<10),odd]` 接受一個函數，他接受一個數值並將他餵給所有的 predicate，包含 `[(>4),(<10),odd]`。然後回傳一串布林值。他將一個型別為 `(Num a) => [a -> Bool]` 的 list 變成一個型別為 `(Num a) => a -> [Bool]` 的函數，很酷吧。

由於 list 要求裡面元素的型別要一致，所以包含在 list 中的所有函數都是同樣型別。你不能創造一個像是 `[ord, (+3)]` 這樣的 list，因為 `ord` 接受一個字元並回傳一個數值，然而 `(+3)` 接受一個數值並回傳一個數值。

當跟 `[]` 一起使用的時候，`sequenceA` 接受一串 list，並回傳另一串 list。他實際上是創建一個包含所有可能組合的 list。為了方便說明，我們比較一下使用 `sequenceA` 跟 list comprehension 的差異：

```haskell
ghci> sequenceA [[1,2,3],[4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> [[x,y] | x <- [1,2,3], y <- [4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> sequenceA [[1,2],[3,4]]  
[[1,3],[1,4],[2,3],[2,4]]  
ghci> [[x,y] | x <- [1,2], y <- [3,4]]  
[[1,3],[1,4],[2,3],[2,4]]  
ghci> sequenceA [[1,2],[3,4],[5,6]]  
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]  
ghci> [[x,y,z] | x <- [1,2], y <- [3,4], z <- [5,6]]  
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
```

這可能有點難以理解，但如果你多做點嘗試，你會比較能看出來些眉目。假設我們在做 `sequenceA [[1,2],[3,4]]`。要知道這是怎麼回事，我們首先用 `sequenceA` 的定義 `sequenceA (x:xs) = (:) <$> x <*> sequenceA xs` 還有邊界條件 `sequenceA [] = pure []` 來看看。你不需要實際計算，但他可以幫助你理解 `sequenceA` 是怎麼運作在一串 list 上，畢竟這有點複雜。

```
# 我們從 ``sequenceA [[1,2],[3,4]]`` 開始
# 那可以被計算成 ``(:) <$> [1,2] <*> sequenceA [[3,4]]``
# 計算內層的 ``sequenceA``，會得到 ``(:) <$> [1,2] <*> ((:) <$> [3,4] <*> sequenceA [])``
# 我們碰到了邊界條件，所以會是 ``(:) <$> [1,2] <*> ((:) <$> [3,4] <*> [[]])``
# 現在我們計算 ``(:) <$> [3,4] <*> [[]] `` 的部份，我們會對左邊 list 中的每一個值 (也就是 ``3`` 跟 ``4``) 跟右邊的每一個值 (只有 ``[]``)套用 ``:``，而得到 ``[3:[], 4:[]]``，也就是 ``[[3],[4]]``。所以我們有 ``(:) <$> [1,2] <*> [[3],[4]]``
# 而對於左邊的每一個值(``1`` 跟 ``2``)以及右邊可能的值（``[3]`` 跟 ``[4]``）我們套用 ``:`` 而得到 ``[1:[3], 1:[4], 2:[3], 2:[4]]``，他等於 ``[[1,3],[1,4],[2,3],[2,4]]``
```

計算 `(+) <$> [1,2] <*> [4,5,6]` 會得到一個 non-deterministic 的結果 `x + y`，其中 `x` 代表 `[1,2]` 中的每一個值，而 `y` 代表 `[4,5,6]` 中的每一個值。我們用 list 來表示每一種可能的情形。同樣的，當我們在做 `sequence [[1,2],[3,4],[5,6],[7,8]]`，他的結果會是 non-deterministic 的 `[x,y,z,w]`，其中 `x` 代表 `[1,2]` 中的每一個值，而 `y` 代表 `[3,4]` 中的每一個值。以此類推。我們用 list 代表 non-deterministic 的計算，每一個元素都是一個可能的情形。這也是為什麽會用到 list of list。

當使用在 I/O action 上的時候，`sequenceA` 跟 `sequence` 是等價的。他接受一串 I/O action 並回傳一個 I/O action，這個 I/O action 會計算 list 中的每一個 I/O action，並把結果放在一個 list 中。要將型別為 `[IO a]` 的值轉換成 `IO [a]` 的值，也就是會產生一串 list 的一個 I/O action，那這些 I/O action 必須要一個一個地被計算，畢竟對於這些 I/O action 你沒辦法不計算就得到結果。

```haskell
ghci> sequenceA [getLine, getLine, getLine]  
heyh  
ho  
woo  
["heyh","ho","woo"]
```

就像普通的函數一樣，applicative functors 也遵循一些定律。其中最重要的一個是我們之前提過的 `pure f <*> x = fmap f x`。你可以證明一些我們之前介紹過的 applicative functor 遵守這個定律當作練習。其他的 functors law 有：

```
# ``pure id <*> v = v``
# ``pure (.) <*> u <*> v <*> w = u <*> (v <*> w)``
# ``pure f <*> pure x = pure (f x)``
# ``u <*> pure y = pure ($ y) <*> u``
```

我們不會一項一項地細看，那樣會花費很大的篇幅而且對讀者來說很無聊，但如果你有興趣，你可以針對某些 instance 看看他們會不會遵守。

結論就是 applicative functor 不只是有趣而且實用， 他允許我們結合不同種類的計算，像是 I/O 計算，non-deterministic 的計算，有可能失敗的計算等等。而使用 `<$>` 跟 `<*>` 我們可以將普通的函數來運作在任意數量的 applicative functors 上。

## 關鍵字"newtype"

![](https://github.com/mno2/learnyouahaskell-zh/tree/689e7bcf954b5c0011bf6a515895f6c9a4706017/zh-tw/ch11/maoi.png)

到目前為止，我們已經看過了如何用 `data` 關鍵字定義自己的 algebraic data type。我們也學習到了如何用 `type` 來定義 type synonyms。在這個章節中，我們會看一下如何使用 `newtype` 來從一個現有的型別中定義出新的型別，並說明我們為什麽會想要那麼做。

在之前的章節中，我們瞭解到其實 list 有很多種方式可以被視為一種 applicative functor。一中方式是定義 `<*>` 將左邊的每一個值跟右邊的每一個值組合，而得到各種組合的結果。

```haskell
ghci> [(+1),(*100),(*5)] <*> [1,2,3]  
[2,3,4,100,200,300,5,10,15]
```

第二種方式是將 `<*>` 定義成將左邊的第一個函數套用至右邊的第一個值，然後將左邊第二個函數套用至右邊第二個值。以此類推。最終，這表現得有點像將兩個 list 用一個拉鍊拉起來一樣。但由於 list 已經被定義成 `Applicaitive` 的 instance 了，所以我們要怎麼要讓 list 可以被定義成第二種方式呢？如果你還記得我們說過我們是有很好的理由定義了 `ZipList a`，其中他裡面只包含一個值構造子跟只包含一個欄位。其實他的理由就是要讓 `ZipList` 定義成用拉鍊的方式來表現 applicative 行為。我們只不過用 `ZipList` 這個構造子將他包起來，然後用 `getZipList` 來解開來。

```haskell
ghci> getZipList $ ZipList [(+1),(*100),(*5)] <*> ZipList [1,2,3]  
[2,200,15]
```

所以這跟 newtype 這個關鍵字有什麽關係呢？想想看我們是怎麼宣告我們的 `ZipList a` 的，一種方式是像這樣：

```haskell
data ZipList a = ZipList [a]
```

也就是一個只有一個值構造子的型別而且那個構造子裡面只有一個欄位。我們也可以用 record syntax 來定義一個解開的函數：

```haskell
data ZipList a = ZipList { getZipList :: [a] }
```

這樣聽起來不錯。這樣我們就有兩種方式來讓一個型別來表現一個 typeclass，我們可以用 `data` 關鍵字來把一個型別包在另一個裡面，然後再將他定義成第二種表現方式。

而在 Haskell 中 `newtype` 正是為了這種情形，我們想將一個型別包在另一個型別中。在實際的函式庫中 `ZipList a` 是這樣定義了：

```haskell
newtype ZipList a = ZipList { getZipList :: [a] }
```

這邊我們不用 `data` 關鍵字反而是用 `newtype` 關鍵字。這是為什麽呢？第一個理由是 `newtype` 比較快速。如果你用 `data` 關鍵字來包一個型別的話，在你執行的時候會有一些包起來跟解開來的成本。但如果你用 `newtype` 的話，Haskell 會知道你只是要將一個現有的型別包成一個新的型別，你想要內部運作完全一樣但只是要一個全新的型別而已。有了這個概念，Haskell 可以將包裹跟解開來的成本都去除掉。

那為什麽我們不是一直使用 `newtype` 呢？當你用 `newtype` 來製作一個新的型別時，你只能定義單一一個值構造子，而且那個構造子只能有一個欄位。但使用 `data` 的話，你可以讓那個型別有好幾個值構造子，並且每個構造子可以有零個或多個欄位。

```haskell
data Profession = Fighter | Archer | Accountant  

data Race = Human | Elf | Orc | Goblin  

data PlayerCharacter = PlayerCharacter Race Profession
```

當使用 `newtype`的時候，你是被限制只能用一個值構造子跟單一欄位。

對於 `newtype` 我們也能使用 `deriving` 關鍵字。我們可以 derive 像是 `Eq`, `Ord`, `Enum`, `Bounded`, `Show` 跟 `Read` 的 instance。如果我們想要對新的型別做 derive，那原本的型別必須已經在那個 typeclass 中。這樣很合理，畢竟 `newtype` 就是要將現有的型別包起來。如果我們按照下面的方式定義的話，我們就能對我們的型別做印出以及比較相等性的操作：

```haskell
newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show)
```

我們來跑跑看：

```haskell
ghci> CharList "this will be shown!"  
CharList {getCharList = "this will be shown!"}  
ghci> CharList "benny" == CharList "benny"  
True  
ghci> CharList "benny" == CharList "oisters"  
False
```

對於這個 `newtype`，他的值構造子有下列型別：

```haskell
CharList :: [Char] -> CharList
```

他接受一個 `[Char]` 的值，例如 `"my sharona"` 並回傳一個 `CharList` 的值。從上面我們使用 `CharList` 的值構造子的範例中，我們可以看到的確是這樣。相反地，`getCharList` 具有下列的型別。

```haskell
getCharList :: CharList -> [Char]
```

他接受一個 `CharList` 的值並將他轉成 `[Char]`。你可以將這個想成包裝跟解開的動作，但你也可以將他想成從一個型別轉成另一個型別。

### Using newtype to make type class instances

有好幾次我們想要讓我們的型別屬於某個 typeclass，但型別變數並沒有符合我們想要的。要把 `Maybe` 定義成 `Functor` 的 instance 很容易，因為 `Functor` 這個 typeclass 被定義如下：

```haskell
class Functor f where  
    fmap :: (a -> b) -> f a -> f b
```

我們先定義如下：

```haskell
instance Functor Maybe where
```

然後我們實作 `fmap`。當所有的型別變數被填上時，由於 `Maybe` 取代了 `Functor` 中 `f` 的位置，所以如果我們看看 `fmap` 運作在 `Maybe` 上時是什麽樣，他會像這樣：

```haskell
fmap :: (a -> b) -> Maybe a -> Maybe b
```

![](/files/-LjUmuYWRjy2qUPOE0ZD)

看起來不錯吧？現在我們想要 tuple 成為 `Functor` 的一個 instance，所以當我們用 `fmap` 來 map over 一個 tuple 時，他會先套用到 tuple 中的第一個元素。這樣當我們做 `fmap (+3) (1,1)` 會得到 `(4,1)`。不過要定義出這樣的 instance 有些困難。對於 `Maybe`，我們只要寫 `instance Functor Maybe where`，這是因為對於只吃一個參數的型別構造子我們很容易定義成 `Functor` 的 instance。但對於 `(a,b)` 這樣的就沒辦法。要繞過這樣的困境，我們可以用 `newtype` 來重新定義我們的 tuple，這樣第二個型別參數就代表了 tuple 中的第一個元素部份。

```haskell
newtype Pair b a = Pair { getPair :: (a,b) }
```

現在我們可以將他定義成 `Functor` 的 instance，所以函數被 map over tuple 中的第一個部份。

```haskell
instance Functor (Pair c) where  
    fmap f (Pair (x,y)) = Pair (f x, y)
```

正如你看到的，我們可以對 newtype 定義的型別做模式匹配。我們用模式匹配來拿到底層的 tuple，然後我們將 `f` 來套用至 tuple 的第一個部份，然後我們用 `Pair` 這個值構造子來將 tuple 轉換成 `Pair b a`。如果我們問 `fmap` 的型別究竟是什麽，他會是：

```haskell
fmap :: (a -> b) -> Pair c a -> Pair c b
```

我們說過 `instance Functor (Pair c) where` 跟 `Pair c` 取代了 `Functor` 中 `f` 的位置：

```haskell
class Functor f where  
    fmap :: (a -> b) -> f a -> f b
```

如果我們將一個 tuple 轉換成 `Pair b a`，我們可以用 `fmap` 來 map over 第一個部份。

```haskell
ghci> getPair $ fmap (*100) (Pair (2,3))  
(200,3)  
ghci> getPair $ fmap reverse (Pair ("london calling", 3))  
("gnillac nodnol",3)
```

### On newtype laziness

我們提到 `newtype` 一般來講比 `data` 來得有效率。`newtype` 能做的唯一一件事就是將現有的型別包成新的型別。這樣 Haskell 在內部就能將新的型別的值用舊的方式來操作。只是要記住他們還是不同的型別。這代表 `newtype` 並不只是有效率，他也具備 lazy 的特性。我們來說明一下這是什麽意思。

就像我們之前說得，Haskell 預設是具備 lazy 的特性，這代表只有當我們要將函數的結果印出來的時候計算才會發生。或者說，只有當我們真的需要結果的時候計算才會發生。在 Haskell 中 `undefined` 代表會造成錯誤的計算。如果我們試著計算他，也就是將他印到終端中，Haskell 會丟出錯誤。

```haskell
ghci> undefined  
*** Exception: Prelude.undefined
```

然而，如果我們做一個 list，其中包含一些 `undefined` 的值，但卻要求一個不是 `undefined` 的 head，那一切都會順利地被計算，因為 Haskell 並不需要 list 中其他元素來得到結果。我們僅僅需要看到第一個元素而已。

```haskell
ghci> head [3,4,5,undefined,2,undefined]  
3
```

現在們考慮下面的型別：

```haskell
data CoolBool = CoolBool { getCoolBool :: Bool }
```

這是一個用 `data` 關鍵字定義的 algebraic data type。他有一個值建構子並只有一個型別為 `Bool` 的欄位。我們寫一個函數來對 `CoolBool` 做模式匹配，並回傳一個 `"hello"` 的值。他並不會管 `CoolBool` 中裝的究竟是 `True` 或 `False`。

```haskell
helloMe :: CoolBool -> String  
helloMe (CoolBool _) = "hello"
```

這次我們不餵給這個函數一個普通的 `CoolBool`，而是丟給他一個 `undefined`。

```
ghci> helloMe undefined  
"*** Exception: Prelude.undefined  "
```

結果收到了一個 Exception。是什麽造成這個 Exception 的呢？用 `data` 定義的型別可以有好幾個值構造子（儘管 `CoolBool` 只有一個）所以當我們要看看餵給函數的值是否是 `(CoolBool _)` 的形式，Haskell 會需要做一些基本的計算來看看是哪個值構造子被用到。但當我們計算 `undefined` 的時候，就算是一點也會丟出 Exception。

我們不用 `data` 來定義 `CoolBool` 而用 `newtype`：

```haskell
newtype CoolBool = CoolBool { getCoolBool :: Bool }
```

我們不用修改 `helloMe` 函數，因為對於模式匹配使用 `newtype` 或 `data` 都是一樣。我們再來將 `undefined` 餵給 `helloMe`。

```haskell
ghci> helloMe undefined  
"hello"
```

居然正常運作！為什麽呢？正如我們說過得，當我們使用 `newtype` 的時候，Haskell 內部可以將新的型別用舊的型別來表示。他不必加入另一層 box 來包住舊有的型別。他只要注意他是不同的型別就好了。而且 Haskell 會知道 `newtype` 定義的型別一定只會有一個構造子，他不必計算餵給函數的值就能確定他是 `(CoolBool _)` 的形式，因為 `newtype` 只有一個可能的值跟單一欄位！

這樣行為的差異可能沒什麼關係，但實際上他非常重要。因為他讓我們認知到儘管從撰寫程式的觀點來看沒什麽差異，但他們的確是兩種不同的機制。儘管 `data` 可以讓你從無到有定義型別，`newtype` 是從一個現有的型別做出來的。對 `newtype` 做模式匹配並不是像從盒子中取出東西，他比較像是將一個型別轉換成另一個型別。

### type vs newtype vs data

到目前為止，你也許對於 `type`,`data` 跟 `newtype` 之間的差異還不是很瞭解，讓我們快速複習一遍。

`type` 關鍵字是讓我們定義 type synonyms。他代表我們只是要給一個現有的型別另一個名字，假設我們這樣做：

```haskell
type IntList = [Int]
```

這樣做可以允許我們用 `IntList` 的名稱來指稱 `[Int]`。我們可以交換地使用他們。但我們並不會因此有一個 `IntList` 的值構造子。因為 `[Int]` 跟 `IntList` 只是兩種指稱同一個型別的方式。我們在指稱的時候用哪一個並無所謂。

```haskell
ghci> ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int])  
[1,2,3,1,2,3]
```

當我們想要讓 type signature 更清楚一些，給予我們更瞭解函數的 context 的時候，我們會定義 type synonyms。舉例來說，當我們用一個型別為 `[(String,String)]` 的 association list 來代表一個電話簿的時候，我們可以定義一個 `PhoneBook` 的 type synonym，這樣 type signature 會比較容易讀。

`newtype` 關鍵字將現有的型別包成一個新的型別，大部分是為了要讓他們可以是特定 typeclass 的 instance 而這樣做。當我們使用 `newtype` 來包裹一個現有的型別時，這個型別跟原有的型別是分開的。如果我們將下面的型別用 `newtype` 定義：

```haskell
newtype CharList = CharList { getCharList :: [Char] }
```

我們不能用 `++` 來將 `CharList` 跟 `[Char]` 接在一起。我們也不能用 `++` 來將兩個 `CharList` 接在一起，因為 `++` 只能套用在 list 上，而 `CharList` 並不是 list，儘管你會說他包含一個 list。但我們可以將兩個 `CharList` 轉成 list，將他們 `++` 然後再轉回 `CharList`。

當我們在 `newtype` 宣告中使用 record syntax 的時候，我們會得到將新的型別轉成舊的型別的函數，也就是我們 `newtype` 的值構造子，以及一個函數將他的欄位取出。新的型別並不會被自動定義成原有型別所屬的 typeclass 的一個 instance，所以我們必須自己來 derive 他們。

實際上你可以將 `newtype` 想成是只能定義一個構造子跟一個欄位的 `data` 宣告。如果你碰到這種情形，可以考慮使用 `newtype`。

使用 `data` 關鍵字是為了定義自己的型別。他們可以在 algebraic data type 中放任意數量的構造子跟欄位。可以定義的東西從 list, `Maybe` 到 tree。

如果你只是希望你的 type signature 看起來比較乾淨，你可以只需要 type synonym。如果你想要將現有的型別包起來並定義成一個 type class 的 instance，你可以嘗試使用 newtype。如果你想要定義完全新的型別，那你應該使用 `data` 關鍵字。

## Monoids

Haskell 中 typeclass 是用來表示一個型別之間共有的行為，是一種 interface。我們介紹過 `Eq`，他定義型別是否可以比較相等性，以及 `Ord`，他表示可以被排序的型別。還介紹了更有趣的像是 `Functor` 跟 `Applicative`。

當我們定義一個型別時，我們會想說他應該要支援的行為。也就是表現的行為是什麽，並且要讓他屬於哪些 typeclass。如果希望他可以比較相等與否，那我們就應該定義他成為 `Eq` 的一個 instance。如果我們想要看看型別是否是一種 functor，我們可以定義他是 `Functor` 的一個 instance。以此類推。

考慮 `*` 是一個將兩個數值相乘的一個函數。如果我們將一個數值乘上 `1`，那就會得到自身的數值。我們實際上是做 `1 * x` 或 `x * 1` 並沒有差別。結果永遠會是 `x`。同樣的，`++` 是一個接受兩個參數並回傳新的值的一個函數。只是他不是相乘而是將兩個 list 接在一起。而類似 `*`，他也有一個特定的值，當他跟其他值使用 `++` 時會得到同樣的值。那個值就是空的 list `[]`。

```haskell
ghci> 4 * 1  
4  
ghci> 1 * 9  
9  
ghci> [1,2,3] ++ []  
[1,2,3]  
ghci> [] ++ [0.5, 2.5]  
[0.5,2.5]
```

看起來 `*` 之於 `1` 跟 `++` 之於 `[]` 有類似的性質：

```
# 函數同樣接受兩個參數
# 參數跟回傳值是同樣的型別
# 同樣存在某些值當套用二元函數時並不會改變其他值
```

關於這兩種操作還有另一個比較難察覺的性質就是，當我們對這個二元函數對三個以上的值操作並化簡，函數套用的順序並不會影響到結果。不論是 `(3 * 4) * 5` 或是 `3 * (4 * 5)`，兩種方式都會得到 `60`。而 `++` 也是相同的。

```haskell
ghci> (3 * 2) * (8 * 5)  
240  
ghci> 3 * (2 * (8 * 5))  
240  
ghci> "la" ++ ("di" ++ "da")  
"ladida"  
ghci> ("la" ++ "di") ++ "da"  
"ladida"
```

我們稱呼這樣的性質為結合律(associativity)。`*` 遵守結合律，`++` 也是。但 `-` 就不遵守。`(5 - 3) - 4` 跟 `5 - (3 - 4)` 得到的結果是不同的。

注意到這些性質並具體地寫下來，就可以得到 monoid。一個 monoid 是你有一個遵守結合律的二元函數還有一個可以相對於那個函數作為 identity 的值。當某個值相對於一個函數是一個 identity，他表示當我們將這個值丟給函數時，結果永遠會是另外一邊的那個值本身。`1` 是相對於 `*` 的 identity，而 `[]` 是相對於 `++` 的 identity。在 Haskell 中還有許多其他的 monoid，這也是為什麽我們定義了 `Monoid` 這個 typeclass。他描述了表現成 monoid 的那些型別。我們來看看這個 typeclass 是怎麼被定義的：

```haskell
class Monoid m where  
    mempty :: m  
    mappend :: m -> m -> m  
    mconcat :: [m] -> m  
    mconcat = foldr mappend mempty
```

![](/files/-LjUmuY_wksx_M-SDa0P)

`Monoid` typeclass 被定義在 `import Data.Monoid` 中。我們來花些時間好好瞭解他。

首先我們看到只有具體型別才能定義成 `Monoid` 的 instance。由於在 typeclass 定義中的 `m` 並不接受任何型別參數。這跟 `Functor` 以及 `Applicative` 不同，他們要求他們的 instance 必須是一個接受單一型別參數的型別構造子。

第一個函數是 `mempty`，由於他不接受任何參數，所以他並不是一個函數，而是一個 polymorphic 的常數。有點像是 `Bounded` 中的 `minBound` 一樣。`mempty` 表示一個特定 monoid 的 identity。

再來我們看到 `mappend`，你可能已經猜到，他是一個接受兩個相同型別的值的二元函數，並回傳同樣的型別。不過要注意的是他的名字不太符合他真正的意思，他的名字隱含了我們要將兩個東西接在一起。儘管在 list 的情況下 `++` 的確將兩個 list 接起來，但 `*` 則否。他只不過將兩個數值做相乘。當我們再看到其他 `Monoid` 的 instance 時，我們會看到他們大部分都沒有接起來的做，所以不要用接起來的概念來想像 `mappend`，只要想像他們是接受兩個 monoid 的值並回傳另外一個就好了。

在 typeclass 定義中的最後一個函數是 `mconcat`。他接受一串 monoid 值，並將他們用 `mappend` 簡化成單一的值。他有一個預設的實作，就是從 `mempty` 作為起始值，然後用 `mappend` 來 fold。由於對於大部分的 instance 預設的實作就沒什麼問題，我們不會想要實作自己的 `mconcat`。當我們定義一個型別屬於 `Monoid` 的時候，多半實作 `mempty` 跟 `mappend` 就可以了。而 `mconcat` 就是因為對於一些 instance，有可能有比較有效率的方式來實作 `mconcat`。不過大多數情況都不需要。

在我們繼續接下去看幾個 `Monoid` 的例子前，我們來看一下 monoid law。我們提過必須有一個值作為 identity 以及一個遵守結合律的二元函數當作前提。我們是可以定義一個 `Monoid` 的 instance 卻不遵守這些定律的，但這樣寫出來的 instance 就沒有用了，因為我們在使用 `Monoid` 的時候都是依靠這些定律才可以稱作實質上的 monoid。所以我們必須確保他們遵守：

```
# ``mempty `mappend` x = x``
# ``x `mappend` mempty = x``
# ``(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)``
```

前兩個描述了 `mempty` 相對於 `mappend` 必須要表現成 identity。而第三個定律說了 `mappend` 必須要遵守結合律。也就是說我們做 `mappend` 順序並不重要。Haskell 不會自己檢查這些定律是否有被遵守。所以你必須自己小心地檢查他們。

### Lists are monoids

沒錯，list 是一種 monoid。正如我們先前看到的，`++` 跟空的 list `[]` 共同形成了一個 monoid。他的 instance 很簡單：

```haskell
instance Monoid [a] where  
    mempty = []  
    mappend = (++)
```

list 是 `Monoid` typeclass 的一個 instance，這跟他們裝的元素的型別無關。注意到我們寫 `instance Monoid [a]` 而非 `instance Monoid []`，這是因為 `Monoid` 要求 instance 必須是具體型別。

我們試著跑跑看，得到我們預期中的結果：

```haskell
ghci> [1,2,3] `mappend` [4,5,6]  
[1,2,3,4,5,6]  
ghci> ("one" `mappend` "two") `mappend` "tree"  
"onetwotree"  
ghci> "one" `mappend` ("two" `mappend` "tree")  
"onetwotree"  
ghci> "one" `mappend` "two" `mappend` "tree"  
"onetwotree"  
ghci> "pang" `mappend` mempty  
"pang"  
ghci> mconcat [[1,2],[3,6],[9]]  
[1,2,3,6,9]  
ghci> mempty :: [a]  
[]
```

![](/files/-LjUmuYbsltbEjrYePuK)

注意到最後一行我們明白地標記出型別。這是因為如果只些 `mempty` 的話，GHCi 不會知道他是哪一個 instance 的 `mempty`，所以我們必須清楚說出他是 list instance 的 mempty。我們可以使用一般化的型別 `[a]`，因為空的 list 可以看作是屬於任何型別。

由於 `mconcat` 有一個預設的實作，我們將某個型別定義成 `Monoid` 的型別時就可以自動地得到預設的實作。但對於 list 而言，`mconcat` 其實就是 `concat`。他接受一個裝有 list 的 list，並把他用 `++` 來扁平化他。

list 的 instance 也遵守 monoid law。當我們有好幾個 list 並且用 `mappend` 來把他們串起來，先後順序並不是很重要，因為他們都是接在最後面。而且空的 list 也表現得如 identity 一樣。注意到 monoid 並不要求 ``a `mappend` b`` 等於 ``b `mappend` a``。在 list 的情況下，他們明顯不相等。

```haskell
ghci> "one" `mappend` "two"  
"onetwo"  
ghci> "two" `mappend` "one"  
"twoone"
```

這樣並沒有關系。`3 * 5` 跟 `5 * 3` 會相等只不過是乘法的性質而已，但沒有保證所有 monoid 都要遵守。

### Product and Sum

我們已經描述過將數值表現成一種 monoid 的方式。只要將 `*` 當作二元函數而 `1` 當作 identity 就好了。而且這不是唯一一種方式，另一種方式是將 `+` 作為二元函數而 `0` 作為 identity。

```haskell
ghci> 0 + 4  
4  
ghci> 5 + 0  
5  
ghci> (1 + 3) + 5  
9  
ghci> 1 + (3 + 5)  
9
```

他也遵守 monoid law，因為將 0 加上其他數值，都會是另外一者。而且加法也遵守結合律。所以現在我們有兩種方式來將數值表現成 monoid，那要選哪一個呢？其實我們不必要強迫定下來，還記得當同一種型別有好幾種表現成某個 typeclass 的方式時，我們可以用 `newtype` 來包裹現有的型別，然後再定義新的 instance。這樣就行了。

`Data.Monoid` 這個模組匯出了兩種型別，`Product` 跟 `Sum`。`Product` 定義如下：

```haskell
newtype Product a =  Product { getProduct :: a }  
    deriving (Eq, Ord, Read, Show, Bounded)
```

簡單易懂，就是一個單一型別參數的 `newtype`，並 derive 一些性質。他的 `Monoid` 的 instance 長得像這樣：

```haskell
instance Num a => Monoid (Product a) where  
    mempty = Product 1  
    Product x `mappend` Product y = Product (x * y)
```

`mempty` 只不過是將 `1` 包在 `Product` 中。`mappend` 則對 `Product` 的構造子做模式匹配，將兩個取出的數值相乘後再將結果放回去。就如你看到的，typeclass 定義前面有 `Num a` 的條件限制。所以他代表 `Product a` 對於所有屬於 `Num` 的 `a` 是一個 `Monoid`。要將 `Product a` 作為一個 monoid 使用，我們需要用 newtype 來做包裹跟解開的動作。

```haskell
ghci> getProduct $ Product 3 `mappend` Product 9  
27  
ghci> getProduct $ Product 3 `mappend` mempty  
3  
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2  
24  
ghci> getProduct . mconcat . map Product $ [3,4,2]  
24
```

這當作 `Monoid` 的一個演練還不錯，但並不會有人覺得這會比 `3 * 9` 跟 `3 * 1` 這種方式來做乘法要好。但我們稍後會說明儘管像這種顯而易見的定義還是有他方便的地方。

`Sum` 跟 `Product` 定義的方式類似，我們也可以用類似的方式操作：

```haskell
ghci> getSum $ Sum 2 `mappend` Sum 9  
11  
ghci> getSum $ mempty `mappend` Sum 3  
3  
ghci> getSum . mconcat . map Sum $ [1,2,3]  
6
```

### Any and ALL

另一種可以有兩種表示成 monoid 方式的型別是 `Bool`。第一種方式是將 `||` 當作二元函數，而 `False` 作為 identity。這樣的意思是只要有任何一個參數是 `True` 他就回傳 `True`，否則回傳 `False`。所以如果我們使用 `False` 作為 identity，他會在跟 `False` 做 OR 時回傳 `False`，跟 `True` 做 OR 時回傳 `True`。`Any` 這個 newtype 是 `Monoid` 的一個 instance，並定義如下：

```haskell
newtype Any = Any { getAny :: Bool }  
    deriving (Eq, Ord, Read, Show, Bounded)
```

他的 instance 長得像這樣：

```haskell
instance Monoid Any where  
    mempty = Any False  
    Any x `mappend` Any y = Any (x || y)
```

他叫做 `Any` 的理由是 ``x `mappend` y`` 當有任何一個是 `True` 時就會是 `True`。就算是更多個用 `mappend` 串起來的 `Any`，他也會在任何一個是 `True` 回傳 `True`。

```haskell
ghci> getAny $ Any True `mappend` Any False  
True  
ghci> getAny $ mempty `mappend` Any True  
True  
ghci> getAny . mconcat . map Any $ [False, False, False, True]  
True  
ghci> getAny $ mempty `mappend` mempty  
False
```

另一種 `Bool` 表現成 `Monoid` 的方式是用 `&&` 作為二元函數，而 `True` 作為 identity。只有當所有都是 `True` 的時候才會回傳 `True`。下面是他的 newtype 定義：

```haskell
newtype All = All { getAll :: Bool }  
        deriving (Eq, Ord, Read, Show, Bounded)
```

而這是他的 instance：

```haskell
instance Monoid All where  
        mempty = All True  
        All x `mappend` All y = All (x && y)
```

當我們用 `mappend` 來串起 `All` 型別的值時，結果只有當所有 `mappend` 的值是 `True` 時才會是 `True`：

```haskell
ghci> getAll $ mempty `mappend` All True  
True  
ghci> getAll $ mempty `mappend` All False  
False  
ghci> getAll . mconcat . map All $ [True, True, True]  
True  
ghci> getAll . mconcat . map All $ [True, True, False]  
False
```

就如乘法跟加法一樣，我們通常寧願用二元函數來操作他們也不會用 newtype 來將他們包起來。不會將他們包成 `Any` 或 `All` 然後用 `mappend`，`mempty` 或 `mconcat` 來操作。通常使用 `or` 跟 `and`，他們接受一串 `Bool`，並只有當任意一個或是所有都是 `True` 的時候才回傳 `True`。

### The Ordering monoid

還記得 `Ordering` 型別嗎?他是比較運算之後得到的結果，包含三個值：`LT`，`EQ` 跟 `GT`，分別代表小於，等於跟大於：

```haskell
ghci> 1 `compare` 2  
LT  
ghci> 2 `compare` 2  
EQ  
ghci> 3 `compare` 2  
GT
```

針對 list，數值跟布林值而言，要找出 monoid 的行為只要去檢視已經定義的函數，然後看看有沒有展現出 monoid 的特性就可以了，但對於 `Ordering`，我們就必須要更仔細一點才能看出來是否是一個 monoid，但其實他的 `Monoid` instance 還蠻直覺的：

```haskell
instance Monoid Ordering where  
    mempty = EQ  
    LT `mappend` _ = LT  
    EQ `mappend` y = y  
    GT `mappend` _ = GT
```

![](/files/-LjUmuYe5bfxhTqV3aTa)

這個 instance 定義如下：當我們用 `mappend` 兩個 `Ordering` 型別的值時，左邊的會被保留下來。除非左邊的值是 `EQ`，那我們就會保留右邊的當作結果。而 identity 就是 `EQ`。乍看之下有點隨便，但實際上他是我們比較兩個英文字時所用的方法。我們先比較兩個字母是否相等，如果他們不一樣，那我們就知道那一個字在字典中會在前面。而如果兩個字母相等，那我們就繼續比較下一個字母，以此類推。

舉例來說，如果我們字典順序地比較 `"ox"` 跟 `"on"` 的話。我們會先比較兩個字的首個字母，看看他們是否相等，然後繼續比較第二個字母。我們看到 `'x'` 是比 `'n'` 要來得大，所以我們就知道如何比較兩個字了。而要瞭解為何 `EQ` 是 identity，我們可以注意到如果我們在兩個字中間的同樣位置塞入同樣的字母，那他們之間的字典順序並不會改變。`"oix"` 仍然比 `"oin"` 要大。

很重要的一件事是在 `Ordering` 的 `Monoid` 定義裡 ``x `mappend` y`` 並不等於 ``y `mappend` x``。因為除非第一個參數是 `EQ`，不然結果就會是第一個參數。所以 ``LT `mappend` GT`` 等於 `LT`，然而 ``GT `mappend` LT`` 等於 `GT`。

```haskell
ghci> LT `mappend` GT  
LT  
ghci> GT `mappend` LT  
GT  
ghci> mempty `mappend` LT  
LT  
ghci> mempty `mappend` GT  
GT
```

所以這個 monoid 在什麽情況下會有用呢？假設你要寫一個比較兩個字串長度的函數，並回傳 `Ordering`。而且當字串一樣長的時候，我們不直接回傳 `EQ`，反而繼續用字典順序比較他們。一種實作的方式如下：

```haskell
lengthCompare :: String -> String -> Ordering  
lengthCompare x y = let a = length x `compare` length y   
                        b = x `compare` y  
                    in  if a == EQ then b else a
```

我們稱呼比較長度的結果為 `a`，而比較字典順序的結果為 `b`，而當長度一樣時，我們就回傳字典順序。

如果善用我們 `Ordering` 是一種 monoid 這項知識，我們可以把我們的函數寫得更簡單些：

```haskell
import Data.Monoid

lengthCompare :: String -> String -> Ordering  
lengthCompare x y = (length x `compare` length y) `mappend`  
                    (x `compare` y)
```

我們可以試著跑跑看：

```haskell
ghci> lengthCompare "zen" "ants"  
LT  
ghci> lengthCompare "zen" "ant"  
GT
```

要記住當我們使用 `mappend`。他在左邊不等於 `EQ` 的情況下都會回傳左邊的值。相反地則回傳右邊的值。這也是為什麽我們將我們認為比較重要的順序放在左邊的參數。如果我們要繼續延展這個函數，要讓他們比較母音的順序，並把這順序列為第二重要，那我們可以這樣修改他：

```haskell
import Data.Monoid  

lengthCompare :: String -> String -> Ordering  
lengthCompare x y = (length x `compare` length y) `mappend`  
                    (vowels x `compare` vowels y) `mappend`  
                    (x `compare` y)  
    where vowels = length . filter (`elem` "aeiou")
```

我們寫了一個輔助函數，他接受一個字串並回傳他有多少母音。他是先用 filter 來把字母濾到剩下 `"aeiou"`，然後再用 `length` 計算長度。

```haskell
ghci> lengthCompare "zen" "anna"  
LT  
ghci> lengthCompare "zen" "ana"  
LT  
ghci> lengthCompare "zen" "ann"  
GT
```

在第一個例子中我們看到長度不同所以回傳 `LT`，明顯地 `"zen"` 要短於 `"anna"`。在第二個例子中，長度是一樣的，但第二個字串有比較多的母音，所以結果仍然是 `LT`。在第三個範例中，兩個長度都相等，他們也有相同個數的母音，經由字典順序比較後得到 `"zen"` 比較大。

`Ordering` 的 monoid 允許我們用不同方式比較事物，並將這些順序也定義了依重要度不同的一個順序。

### Maybe the monoid

我們來看一下 `Maybe a` 是怎樣有多種方式來表現成 `Monoid` 的，並且說明哪些是比較有用的。一種將 `Maybe a` 當作 monoid 的方式就是他的 `a` 也是一個 monoid，而我們將 `mappend` 實作成使用包在 `Just` 裡面的值對應的 `mappend`。並且用 `Nothing` 當作 identity。所以如果我 `mappend` 兩個參數中有一個是 `Nothing`。那結果就會是另一邊的值。他的 instance 定義如下：

```haskell
instance Monoid a => Monoid (Maybe a) where  
    mempty = Nothing  
    Nothing `mappend` m = m  
    m `mappend` Nothing = m  
    Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
```

留意到 class constraint。他說明 `Maybe a` 只有在 `a` 是 `Monoid` 的情況下才會是一個 `Monoid`。如果我們 `mappend` 某個東西跟 `Nothing`。那結果就會是某個東西。如果我們 `mappend` 兩個 `Just`，那 `Just` 包住的結果就會 `mappended` 在一起並放回 `Just`。我們能這麼做是因為 class constraint 保證了在 `Just` 中的值是 `Monoid`。

```haskell
ghci> Nothing `mappend` Just "andy"  
Just "andy"  
ghci> Just LT `mappend` Nothing  
Just LT  
ghci> Just (Sum 3) `mappend` Just (Sum 4)  
Just (Sum {getSum = 7})
```

這當你在處理有可能失敗的 monoid 的時候比較有用。有了這個 instance，我們就不必一一去檢查他們是否失敗，是否是 `Nothing` 或是 `Just`，我們可以直接將他們當作普通的 monoid。

但如果在 `Maybe` 中的型別不是 `Monoid` 呢？注意到在先前的 instance 定義中，唯一有依賴於 monoid 限制的情況就是在 `mappend` 兩個 `Just` 的時候。但如果我們不知道包在 `Just` 裡面的值究竟是不是 monoid，我們根本無法用 `mappend` 操作他們，所以該怎麼辦呢？一種方式就是直接丟掉第二個值而留下第一個值。這就是 `First a` 存在的目的，而這是他的定義：

```haskell
newtype First a = First { getFirst :: Maybe a }  
    deriving (Eq, Ord, Read, Show)
```

我們接受一個 `Maybe a` 並把他包成 newtype，`Monoid` 的定義如下：

```haskell
instance Monoid (First a) where  
    mempty = First Nothing  
    First (Just x) `mappend` _ = First (Just x)  
    First Nothing `mappend` x = x
```

正如我們說過得，`mempty` 就是包在 `First` 中的 `Nothing`。如果 `mappend` 的第一個參數是 `Just`，我們就直接忽略第二個參數。如果第一個參數是 `Nothing`，那我們就將第二個參數當作結果。並不管他究竟是 `Just` 或是 `Nothing`：

```haskell
ghci> getFirst $ First (Just 'a') `mappend` First (Just 'b')  
Just 'a'  
ghci> getFirst $ First Nothing `mappend` First (Just 'b')  
Just 'b'  
ghci> getFirst $ First (Just 'a') `mappend` First Nothing  
Just 'a'
```

`First` 在我們有一大串 `Maybe` 而且想知道他們之中就竟有沒有 `Just` 的時候很有用。可以利用 `mconcat`：

```haskell
ghci> getFirst . mconcat . map First $ [Nothing, Just 9, Just 10]  
Just 9
```

如果我們希望定義一個 `Maybe a` 的 monoid，讓他當 `mappend` 的兩個參數都是 `Just` 的時候將第二個參數當作結果。`Data.Monoid` 中有一個現成的 `Last a`，他很像是 `First a`，只差在 `mappend` 跟 `mconcat` 會保留最後一個非 `Nothing` 的值。

```haskell
ghci> getLast . mconcat . map Last $ [Nothing, Just 9, Just 10]  
Just 10  
ghci> getLast $ Last (Just "one") `mappend` Last (Just "two")  
Just "two"
```

### Using monoids to fold data structures

另一種有趣的 monoid 使用方式就是讓他來幫助我們 fold 一些資料結構。到目前為止我們只有 fold list。但 list 並不是唯一一種可以 fold 的資料結構。我們幾乎可以 fold 任何一種資料結構。像是 tree 也是一種常見的可以 fold 的資料結構。

由於有太多種資料結構可以 fold 了，所以我們定義了 `Foldable` 這個 typeclass。就像 `Functor` 是定義可以 map over 的結構。`Foldable` 是定義可以 fold 的結構。在 `Data.Foldable` 中有定義了一些有用的函數，但他們名稱跟 `Prelude` 中的名稱衝突。所以最好是用 qualified 的方式 import 他們：

```haskell
import qualified Foldable as F
```

為了少打一些字，我們將他們 import qualified 成 `F`。所以這個 typeclass 中定義了哪些函數呢？有 `foldr`，`foldl`，`foldr1` 跟 `foldl1`。你會說我們已經知道這些函數了，他們有什麽不一樣的地方嗎？我們來比較一下 `Foldable` 中的 `foldr` 跟 `Prelude` 中的 `foldr` 的型別異同：

```haskell
ghci> :t foldr  
foldr :: (a -> b -> b) -> b -> [a] -> b  
ghci> :t F.foldr  
F.foldr :: (F.Foldable t) => (a -> b -> b) -> b -> t a -> b
```

儘管 `foldr` 接受一個 list 並將他 fold 起來，`Data.Foldable` 中的 `foldr` 接受任何可以 fold 的型別。並不只是 list。 而兩個 `foldr` 對於 list 的結果是相同的：

```haskell
ghci> foldr (*) 1 [1,2,3]  
6  
ghci> F.foldr (*) 1 [1,2,3]  
6
```

那有哪些資料結構支援 fold 呢？首先我們有 `Maybe`：

```haskell
ghci> F.foldl (+) 2 (Just 9)  
11  
ghci> F.foldr (||) False (Just True)  
True
```

但 fold 一個 `Maybe` 並沒什麽新意。畢竟當他是 `Just` 的時候表現得像是只有單一元素的 list，而當他是 `Nothing` 的時候就像是空的 list 一樣。所以我們來看一些比較複雜的資料結構。

還記得 Making Our Own Types and Typeclass 章節中的樹狀的資料結構嗎？我們是這樣定義的：

```haskell
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
```

我們說一棵樹要不就是一棵空的樹要不然就是一個包含值的節點，並且還指向另外兩棵樹。定義他之後，我們將他定義成 `Functor` 的 instance，因此可以 `fmap` 他。現在我們要將他定義成 `Foldable` 的 instance，這樣我們就可以 fold 他。要定義成 `Foldable` 的一種方式就是實作 `foldr`。但另一種比較簡單的方式就是實作 `foldMap`，他也屬於 `Foldable` typeclass。`foldMap` 的型別如下：

```haskell
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
```

第一個參數是一個函數，這個函數接受 foldable 資料結構中包含的元素的型別，並回傳一個 monoid。他第二個參數是一個 foldable 的結構，並包含型別 `a` 的元素。他將第一個函數來 map over 這個 foldable 的結構，因此得到一個包含 monoid 的 foldable 結構。然後用 `mappend` 來簡化這些 monoid，最後得到單一的一個 monoid。這個函數聽起來不太容易理解，但我們下面會看到他其實很容易實作。而且好消息是只要實作了這個函數就可以讓我們的函數成為 `Foldable`。所以我們只要實作某個型別的 `foldMap`，我們就可以得到那個型別的 `foldr` 跟 `foldl`。

這就是我們如何定義 `Tree` 成為 `Foldable` 的：

```haskell
instance F.Foldable Tree where  
    foldMap f Empty = mempty  
    foldMap f (Node x l r) = F.foldMap f l `mappend`  
                                f x           `mappend`  
                                F.foldMap f r
```

![](/files/-LjUmuZFBfnRui4QGtY8)

我們是這樣思考的：如果我們寫一個函數，他接受樹中的一個元素並回傳一個 monoid，那我們要怎麼簡化整棵樹到只有單一一個 monoid？當我們在對樹做 `fmap` 的時候，我們將那函數套用至節點上，並遞迴地套用至左子樹以及右子樹。這邊我們不只是 map 一個函數而已，我們還要求要把結果用 `mappend` 簡化成只有單一一個 monoid 值。首先我們考慮樹為空的情形，一棵沒有值也沒有子樹的情形。由於沒有值我們也沒辦法將他套用上面轉換成 monoid 的函數，所以當樹為空的時候，結果應該要是 `mempty`。

在非空節點的情形下比較有趣，他包含一個值跟兩棵子樹。在這種情況下，我們遞迴地做 `foldMap`，用 `f` 來套用到左子樹跟右子樹上。要記住我們的 `foldMap` 只會得到單一的 monoid 值。我們也會套用 `f` 到節點中的值。這樣我們就得到三個 monoid 值，有兩個來自簡化子樹的結果，還有一個是套用 `f` 到節點中的值的結果。而我們需要將這三個值整合成單一個值。要達成這個目的我們使用 `mappend`，而且自然地會想到照左子樹，節點值以及右子樹的順序來簡化。

注意到我們並不一定要提供一個將普通值轉成 monoid 的函數。我們只是把他當作是 `foldMap` 的參數，我們要決定的只是如何套用那個函數，來把得到的 monoid 們簡化成單一結果。

現在我們有樹的 `Foldable` instance，而 `foldr` 跟 `foldl` 也有預設的實作了。考慮下面這棵樹：

```haskell
testTree = Node 5  
            (Node 3  
             (Node 1 Empty Empty)  
             (Node 6 Empty Empty)  
            )  
            (Node 9  
             (Node 8 Empty Empty)  
             (Node 10 Empty Empty)  
            )
```

他的 root 是 `5`，而他左邊下來分別是 `3`，再來是 `1` 跟 `6`。而右邊下來是 `9`，再來是 `8` 跟 `10`。有了 `Foldable` 的定義，我們就能像對 list 做 fold 一樣對樹做 fold：

```haskell
ghci> F.foldl (+) 0 testTree  
42  
ghci> F.foldl (*) 1 testTree  
64800
```

`foldMap` 不只是定義 `Foldable` 新的 instance 有用。他也對簡化我們的結構至單一 monoid 值有用。舉例來說，如果我們想要知道我們的樹中有沒有 `3`，我們可以這樣做：

```haskell
ghci> getAny $ F.foldMap (\x -> Any $ x == 3) testTree  
True
```

這邊 `\x -> Any $ x == 3` 是一個接受一個數值並回傳一個 monoid 的函數，也就是一個包在 `Any` 中的 `Bool`。`foldMap` 將這個函數套用至樹的每一個節點，並把結果用 `mappend` 簡化成單一 monoid。如果我們這樣做：

```haskell
ghci> getAny $ F.foldMap (\x -> Any $ x > 15) testTree  
False
```

經過套用 lambda 之後我們所有的節點都會是 `Any False`。但 `mappend` 必須要至少吃到一個 `True` 才能讓最後的結果變成 `True`。這也是為什麽結果會是 `False`，因為我們樹中所有的值都小於等於 `15`。

我們也能將 `foldMap` 配合 `\x -> [x]` 使用來將我們的樹轉成 list。經過套用那個函數後，所有節點都變成包含單一元素的 list。最後用 `mappend` 將這些單一元素的 list 轉成一個裝有全部元素的 list：

```haskell
ghci> F.foldMap (\x -> [x]) testTree  
[1,3,6,5,8,9,10]
```

這個小技巧並不限於樹而已，他可以被套用在任何 `Foldable` 上。


# ch12


# 來看看幾種 Monad

當我們第一次談到 Functor 的時候，我們了解到他是一個抽象概念，代表是一種可以被 map over 的值。然後我們再將其概念提升到 Applicative Functor，他代表一種帶有 context 的型態，我們可以用函數操作他而且同時還保有他的 context。

在這一章，我們會學到 Monad，基本上他是一種加強版的 Applicative Functor，正如 Applicative Functor 是 Functor 的加強版一樣。

![](/files/-LjUmqdyL1Kc5yqiQXjK)

我們介紹到 Functor 是因為我們觀察到有許多型態都可以被 function 給 map over，了解到這個目的，便抽象化了 `Functor` 這個 typeclass 出來。但這讓我們想問：如果給定一個 `a -> b` 的函數以及 `f a` 的型態，我們要如何將函數 map over 這個型態而得到 `f b`？我們知道要如何 map over `Maybe a`，`[a]` 以及 `IO a`。我們甚至還知道如何用 `a -> b` map over `r -> a`，並且會得到 `r -> b`。要回答這個問題，我們只需要看 `fmap` 的型態就好了：

```haskell
fmap :: (Functor f) => (a -> b) -> f a -> f b
```

然後只要針對 `Functor` instance 撰寫對應的實作。

之後我們又看到一些可以針對 Functor 改進的地方，例如 `a -> b` 也被包在一個 Functor value 裡面呢？像是 `Just (*3)`，我們要如何 apply `Just 5` 給他？如果我們不要 apply `Just 5` 而是 `Nothing` 呢？甚至給定 `[(*2),(+4)]`，我們要如何 apply 他們到 `[1,2,3]` 呢？對於此，我們抽象出 `Applicative` typeclass，這就是我們想要問的問題：

```haskell
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
```

我們也看到我們可以將一個正常的值包在一個資料型態中。例如說我們可以拿一個 `1` 然後把他包成 `Just 1`。或是把他包成 `[1]`。也可以是一個 I/O action 會產生一個 `1`。這樣包裝的 function 我們叫他做 `pure`。

如我們說得，一個 applicative value 可以被看作一個有附加 context 的值。例如說，`'a'` 只是一個普通的字元，但 `Just 'a'` 是一個附加了 context 的字元。他不是 `Char` 而是 `Maybe Char`，這型態告訴我們這個值可能是一個字元，也可能什麼都沒有。

來看看 `Applicative` typeclass 怎樣讓我們用普通的 function 操作他們，同時還保有 context：

```haskell
ghci> (*) <$> Just 2 <*> Just 8  
Just 16  
ghci> (++) <$> Just "klingon" <*> Nothing  
Nothing  
ghci> (-) <$> [3,4] <*> [1,2,3]  
[2,1,0,3,2,1]
```

所以我們可以視他們為 applicative values，`Maybe a` 代表可能會失敗的 computation，`[a]` 代表同時有好多結果的 computation (non-deterministic computation)，而 `IO a` 代表會有 side-effects 的 computation。

Monad 是一個從 Applicative functors 很自然的一個演進結果。對於他們我們主要考量的點是：如果你有一個具有 context 的值 `m a`，你能如何把他丟進一個只接受普通值 `a` 的函數中，並回傳一個具有 context 的值？也就是說，你如何套用一個型態為 `a -> m b` 的函數至 `m a`？基本上，我們要求的函數是：

```haskell
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
```

如果我們有一個漂亮的值跟一個函數接受普通的值但回傳漂亮的值，那我們要如何要把漂亮的值丟進函數中？這就是我們使用 Monad 時所要考量的事情。我們不寫成 `f a` 而寫成 `m a` 是因為 `m` 代表的是 `Monad`，但 monad 不過就是支援 `>>=` 操作的 applicative functors。`>>=` 我們稱呼他為 bind。

當我們有一個普通值 `a` 跟一個普通函數 `a -> b`，要套用函數是一件很簡單的事。但當你在處理具有 context 的值時，就需要多考慮些東西，要如何把漂亮的值餵進函數中，並如何考慮他們的行為，但你將會了解到他們其實不難。

## 動手做做看: Maybe Monad

![](https://github.com/mno2/learnyouahaskell-zh/tree/689e7bcf954b5c0011bf6a515895f6c9a4706017/zh-tw/ch12/buddha.png)

現在對於什麼是 Monad 已經有了些模糊的概念， 我們來看看要如何讓這概念更具體一些。

不意外地，`Maybe` 是一個 Monad， 所以讓我們對於他多探討些，看看是否能跟我們所知的 Monad 概念結合起來。

```
到這邊要確定你了解什麼是 Applicatives。如果你知道好幾種 ``Applicative`` 的 instance 還有他們代表的意含就更好了，因為 monad 不過就是對 applicative 的概念進行一次升級。
```

一個 `Maybe a` 型態的值代表型態為 `a` 的值而且具備一個可能造成錯誤的 context。而 `Just "dharma"` 的值代表他不是一個 `"dharma"` 的字串就是字串不見時的 `Nothing`。如果你把字串當作計算的結果，`Nothing` 就代表計算失敗了。

當我們把 `Maybe` 視作 functor，我們其實要的是一個 `fmap` 來把一個函數針對其中的元素做套用。他會對 `Just` 中的元素進行套用，要不然就是保留 `Nothing` 的狀態，其代表裡面根本沒有元素。

```haskell
ghci> fmap (++"!") (Just "wisdom")  
Just "wisdom!"  
ghci> fmap (++"!") Nothing  
Nothing
```

或者視為一個 applicative functor，他也有類似的作用。只是 applicative 也把函數包了起來。`Maybe` 作為一個 applicative functor，我們能用 `<*>` 來套用一個存在 `Maybe` 中的函數至包在另外一個 `Maybe` 中的值。他們都必須是包在 `Just` 來代表值存在，要不然其實就是 `Nothing`。當你在想套用函數到值上面的時候，缺少了函數或是值都會造成錯誤，所以這樣做是很合理的。

```haskell
ghci> Just (+3) <*> Just 3  
Just 6  
ghci> Nothing <*> Just "greed"  
Nothing  
ghci> Just ord <*> Nothing  
Nothing
```

當我們用 applicative 的方式套用函數至 `Maybe` 型態的值時，就跟上面描述的差不多。過程中所有值都必須是 `Just`，要不然結果一定會是 `Nothing`。

```haskell
ghci> max <$> Just 3 <*> Just 6  
Just 6  
ghci> max <$> Just 3 <*> Nothing  
Nothing
```

我們來思考一下要怎麼為 `Maybe` 實作 `>>=`。正如我們之前提到的，`>>=` 接受一個 monadic value，以及一個接受普通值的函數，這函數會回傳一個 monadic value。`>>=` 會幫我們套用這個函數到這個 monadic value。在函數只接受普通值的情況俠，函數是如何作到這件事的呢？要作到這件事，他必須要考慮到 monadic value 的 context。

在這個案例中，`>>=` 會接受一個 `Maybe a` 以及一個型態為 `a -> Maybe b` 的函數。他會套用函數到 `Maybe a`。要釐清他怎麼作到的，首先我們注意到 `Maybe` 的 applicative functor 特性。假設我們有一個函數 `\x -> Just (x+1)`。他接受一個數字，把他加 `1` 後再包回 `Just`。

```haskell
ghci> (\x -> Just (x+1)) 1  
Just 2  
ghci> (\x -> Just (x+1)) 100  
Just 101
```

如果我們餵給函數 `1`，他會計算成 `Just 2`。如果我們餵給函數 `100`，那結果便是 `Just 101`。但假如我們餵一個 `Maybe` 的值給函數呢？如果我們把 `Maybe` 想成一個 applicative functor，那答案便很清楚。如果我們拿到一個 `Just`，就把包在 `Just` 裡面的值餵給函數。如果我們拿到一個 `Nothing`，我們就說結果是 `Nothing`。

我們呼叫 `applyMaybe` 而不呼叫 `>>=`。他接受 `Maybe a` 跟一個回傳 `Maybe b` 的函數，並套用函數至 `Maybe a`。

```haskell
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b  
applyMaybe Nothing f  = Nothing  
applyMaybe (Just x) f = f x
```

我們套用一個 infix 函數，這樣 `Maybe` 的值可以寫在左邊且函數是在右邊：

```haskell
ghci> Just 3 `applyMaybe` \x -> Just (x+1)  
Just 4  
ghci> Just "smile" `applyMaybe` \x -> Just (x ++ " :")""  
Just "smile :""  
ghci> Nothing `applyMaybe` \x -> Just (x+1)  
Nothing  
ghci> Nothing `applyMaybe` \x -> Just (x ++ " :")")  
Nothing
```

在上述的範例中，我們看到在套用 `applyMaybe` 的時候，函數是套用在 `Just` 裡面的值。當我們試圖套用到 `Nothing`，那整個結果便是 `Nothing`。假如函數回傳 `Nothing` 呢？

```haskell
ghci> Just 3 `applyMaybe` \x -> if x > 2 then Just x else Nothing  
Just 3  
ghci> Just 1 `applyMaybe` \x -> if x > 2 then Just x else Nothing  
Nothing
```

這正是我們期待的結果。如果左邊的 monadic value 是 `Nothing`，那整個結果就是 `Nothing`。如果右邊的函數是 `Nothing`，那結果也會是 `Nothing`。這跟我們之前把 `Maybe` 當作 applicative 時，過程中有任何一個 `Nothing` 整個結果就會是 `Nothing` 一樣。

對於 `Maybe` 而言，我們已經找到一個方法處理漂亮值的方式。我們作到這件事的同時，也保留了 `Maybe` 代表可能造成錯誤的計算的意義。

你可能會問，這樣的結果有用嗎？由於 applicative functors 讓我們可以拿一個接受普通值的函數，並讓他可以操作具有 context 的值，這樣看起來 applicative functors 好像比 monad 強。但我們會看到 monad 也能作到，因為他只是 applicative functors 的升級版。他們同時也能作到 applicative functors 不能作到的事情。

稍候我們會再繼續探討 `Maybe`，但我們先來看看 monad 的 type class。

## Monad type class

正如 functors 有 `Functor` 這個 type class，而 applicative functors 有一個 `Applicative` 這個 type class，monad 也有他自己的 type class：`Monad` 他看起來像這樣：

```haskell
class Monad m where  
    return :: a -> m a  

    (>>=) :: m a -> (a -> m b) -> m b  

    (>>) :: m a -> m b -> m b  
    x >> y = x >>= \_ -> y  

    fail :: String -> m a  
    fail msg = error msg
```

![](/files/-LjUmqe8OQmfBfeR3XhK)

我們從第一行開始看。他說 `class Monad m where`。但我們之前不是提到 monad 是 applicative functors 的加強版嗎？不是應該有一個限制說一個型態必須先是一個 applicative functor 才可能是一個 monad 嗎？像是 `class (Applicative m) = > Monad m where`。他的確應該要有，但當 Haskell 被創造的早期，人們沒有想到 applicative functor 適合被放進語言中，所以最後沒有這個限制。但的確每個 monad 都是 applicative functor，即使 `Monad` 並沒有這麼宣告。

在 `Monad` typeclass 中定義的第一個函數是 `return`。他其實等價於 `pure`，只是名字不同罷了。他的型態是 `(Monad m) => a -> m a`。他接受一個普通值並把他放進一個最小的 context 中。也就是說他把普通值包進一個 monad 裡面。他跟 `Applicative` 裡面 `pure` 函數做的事情一樣，所以說其實我們已經認識了 `return`。我們已經用過 `return` 來處理一些 I/O。我們用他來做一些假的 I/O，印出一些值。對於 `Maybe` 來說他就是接受一個普通值然後包進 `Just`。

```
提醒一下：``return`` 跟其他語言中的 ``return`` 是完全不一樣的。他並不是結束一個函數的執行，他只不過是把一個普通值包進一個 context 裡面。
```

![](/files/-LjUmqeAcuIc6TO81LKS)

接下來定義的函數是 bind: `>>=`。他就像是函數套用一樣，只差在他不接受普通值，他是接受一個 monadic value（也就是具有 context 的值）並且把他餵給一個接受普通值的函數，並回傳一個 monadic value。

接下來，我們定義了 `>>`。我們不會介紹他，因為他有一個事先定義好的實作，基本上我們在實作 `Monad` typeclass 的時候都不會去理他。

最後一個函數是 `fail`。我們通常在我們程式中不會具體寫出來。他是被 Haskell 用在處理語法錯誤的情況。我們目前不需要太在意 `fail`。

我們知道了 `Monad` typeclass 長什麼樣子，我們來看一下 `Maybe` 的 `Monad` instance。

```haskell
instance Monad Maybe where  
    return x = Just x  
    Nothing >>= f = Nothing  
    Just x >>= f  = f x  
    fail _ = Nothing
```

`return`跟`pure`是等價的。這沒什麼困難的。我們跟我們在定義`Applicative`的時候做一樣的事，只是把他用`Just`包起來。

`>>=`跟我們的`applyMaybe`是一樣的。當我們將`Maybe a`塞給我們的函數，我們保留住context，並且在輸入是`Nothing`的時候回傳`Nothing`。畢竟當沒有值的時候套用我們的函數是沒有意義的。當輸入是`Just`的時候則套用`f`並將他包在`Just`裡面。

我們可以試著感覺一下`Maybe`是怎樣表現成Monad的。

```haskell
ghci> return "WHAT" :: Maybe String  
Just "WHAT"  
ghci> Just 9 >>= \x -> return (x*10)  
Just 90  
ghci> Nothing >>= \x -> return (x*10)  
Nothing
```

第一行沒什麼了不起，我們已經知道 `return` 就是 `pure` 而我們又對 `Maybe` 操作過 `pure` 了。至於下兩行就比較有趣點。

留意我們是如何把 `Just 9` 餵給 `\x -> return (x*10)`。在函數中 `x` 綁定到 `9`。他看起好像我們能不用 pattern matching 的方式就從 `Maybe` 中抽取出值。但我們並沒有喪失掉 `Maybe` 的 context，當他是 `Nothing` 的時候，`>>=` 的結果也會是 `Nothing`。

## 走鋼索

![](/files/-LjUmqeCX7NCfudVNlQq)

我們已經知道要如何把 `Maybe a` 餵進 `a -> Maybe b` 這樣的函數。我們可以看看我們如何重複使用 `>>=` 來處理多個 `Maybe a` 的值。

首先來說個小故事。皮爾斯決定要辭掉他的工作改行試著走鋼索。他對走鋼索蠻在行的，不過仍有個小問題。就是鳥會停在他拿的平衡竿上。他們會飛過來停一小會兒，然後再飛走。這樣的情況在兩邊的鳥的數量一樣時並不是個太大的問題。但有時候，所有的鳥都會想要停在同一邊，皮爾斯就失去了平衡，就會讓他從鋼索上掉下去。

我們這邊假設兩邊的鳥差異在三個之內的時候，皮爾斯仍能保持平衡。所以如果是右邊有一隻，左邊有四隻的話，那還撐得住。但如果左邊有五隻，那就會失去平衡。

我們要寫個程式來模擬整個情況。我們想看看皮爾斯究竟在好幾隻鳥來來去去後是否還能撐住。例如說，我們想看看先來了一隻鳥停在左邊，然後來了四隻停在右邊，然後左邊那隻飛走了。之後會是什麼情形。

我們用一對整數來代表我們的平衡竿狀態。頭一個位置代表左邊的鳥的數量，第二個位置代表右邊的鳥的數量。

```haskell
type Birds = Int  
type Pole = (Birds,Birds)
```

由於我們用整數來代表有多少隻鳥，我們便先來定義 `Int` 的同義型態，叫做 `Birds`。然後我們把 `(Birds, Birds)` 定義成 `Pole`。

接下來，我們定義一個函數他接受一個數字，然後把他放在竿子的左邊，還有另外一個函數放在右邊。

```haskell
landLeft :: Birds -> Pole -> Pole  
landLeft n (left,right) = (left + n,right)  

landRight :: Birds -> Pole -> Pole  
landRight n (left,right) = (left,right + n)
```

我們來試著執行看看：

```haskell
ghci> landLeft 2 (0,0)  
(2,0)  
ghci> landRight 1 (1,2)  
(1,3)  
ghci> landRight (-1) (1,2)  
(1,1)
```

要模擬鳥飛走的話我們只要給定一個負數就好了。 由於這些操作是接受 `Pole` 並回傳 `Pole`， 所以我們可以把函數串在一起。

```haskell
ghci> landLeft 2 (landRight 1 (landLeft 1 (0,0)))  
(3,1)
```

當我們餵 `(0,0)` 給 `landLeft 1` 時，我們會得到 `(1,0)`。接著我們模擬右邊又停了一隻鳥，狀態就變成 `(1,1)`。最後又有兩隻鳥停在左邊，狀態變成 `(3,1)`。我們這邊的寫法是先寫函數名稱，然後再套用參數。但如果先寫 pole 再寫函數名稱會比較清楚，所以我們會想定義一個函數

```haskell
x -: f = f x
```

我們能先套用參數然後再寫函數名稱：

```haskell
ghci> 100 -: (*3)  
300  
ghci> True -: not  
False  
ghci> (0,0) -: landLeft 2  
(2,0)
```

有了這個函數，我們便能寫得比較好讀一些：

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 1 -: landLeft 2  
(3,1)
```

這個範例跟先前的範例是等價的，只不過好讀許多。很清楚的看出我們是從 `(0,0)` 開始，然後停了一隻在左邊，接著右邊又有一隻，最後左邊多了兩隻。

到目前為止沒什麼問題，但如果我們要停 10 隻在左邊呢？

```
ghci> landLeft 10 (0,3)  
(10,3)
```

你說左邊有 10 隻右邊卻只有 3 隻？那不是早就應該掉下去了？這個例子太明顯了，如果換個比較不明顯的例子。

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)  
(0,2)
```

表面看起來沒什麼問題，但如果你仔細看的話，有一瞬間是右邊有四隻，但左邊沒有鳥。要修正這個錯誤，我們要重新檢視 `landLeft` 跟 `landRight`。我們其實是希望這些函數產生失敗的情況。那就是在維持平衡的時候回傳新的 pole，但失敗的時候告訴我們失敗了。這時候 `Maybe` 就剛剛好是我們要的 context 了。我們用 `Maybe` 重新寫一次：

```haskell
landLeft :: Birds -> Pole -> Maybe Pole  
landLeft n (left,right)  
    | abs ((left + n) - right) < 4 = Just (left + n, right)  
    | otherwise                    = Nothing  

landRight :: Birds -> Pole -> Maybe Pole  
landRight n (left,right)  
    | abs (left - (right + n)) < 4 = Just (left, right + n)  
    | otherwise                    = Nothing
```

現在這些函數不回傳 `Pole` 而回傳 `Maybe Pole` 了。他們仍接受鳥的數量跟舊的的 pole，但他們現在會檢查是否有太多鳥會造成皮爾斯失去平衡。我們用 guards 來檢查是否有差異超過三的情況。如果沒有，那就包一個在 `Just` 中的新的 pole，如果是，那就回傳 `Nothing`。

再來執行看看：

```haskell
ghci> landLeft 2 (0,0)  
Just (2,0)  
ghci> landLeft 10 (0,3)  
Nothing
```

一如預期，當皮爾斯不會掉下去的時候，我們就得到一個包在 `Just` 中的新 pole。當太多鳥停在同一邊的時候，我們就會拿到 `Nothing`。這樣很棒，但我們卻不知道怎麼把東西串在一起了。我們不能做 `landLeft 1 (landRight 1 (0,0))`，因為當我們對 `(0,0)` 使用 `landRight 1` 時，我們不是拿到 `Pole` 而是拿到 `Maybe Pole`。`landLeft 1` 會拿到 `Pole` 而不是拿到 `Maybe Pole`。

我們需要一種方法可以把拿到的 `Maybe Pole` 塞到拿 `Pole` 的函數中，然後回傳 `Maybe Pole`。而我們有 `>>=`，他對 `Maybe` 做的事就是我們要的

```haskell
ghci> landRight 1 (0,0) >>= landLeft 2  
Just (2,1)
```

`landLeft 2` 的型態是 `Pole -> Maybe Pole`。我們不能餵給他 `Maybe Pole` 的東西。而 `landRight 1 (0,0)` 的結果就是 `Maybe Pole`，所以我們用 `>>=` 來接受一個有 context 的值然後拿給 `landLeft 2`。`>>=` 的確讓我們把 `Maybe` 當作有 context 的值，因為當我們丟 `Nothing` 給 `landLeft 2` 的時候，結果會是 `Nothing`。

```
ghci> Nothing >>= landLeft 2  
Nothing
```

這樣我們可以把這些新寫的用 `>>=` 串在一起。讓 monadic value 可以餵進只吃普通值的函數。

來看看些例子：

```haskell
ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2  
Just (2,4)
```

我們最開始用 `return` 回傳一個 pole 並把他包在 `Just` 裡面。我們可以像往常套用 `landRight 2`，不過我們不那麼做，我們改用 `>>=`。`Just (0,0)` 被餵到 `landRight 2`，得到 `Just (0,2)`。接著被餵到 `landLeft 2`，得到 `Just (2,2)`。

還記得我們之前引入失敗情況的例子嗎？

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)  
(0,2)
```

之前的例子並不會反應失敗的情況。但如果我們用 `>>=` 的話就可以得到失敗的結果。

```haskell
ghci> return (0,0) >>= landLeft 1 >>= landRight 4 >>= landLeft (-1) >>= landRight (-2)  
Nothing
```

![](/files/-LjUmqeEgGCzyMZRKeZO)

正如預期的，最後的情形代表了失敗的情況。我們再進一步看看這是怎麼產生的。首先 `return` 把 `(0,0)` 放到一個最小的 context 中，得到 `Just (0,0)`。然後是 `Just (0.0) >>= landLeft 1`。由於 `Just (0,0)` 是一個 `Just` 的值。`landLeft 1` 被套用至 `(0,0)` 而得到 `Just (1,0)`。這反應了我們仍保持在平衡的狀態。接著是 `Just (1,0) >>= landright 4` 而得到了 `Just (1,4)`。距離不平衡只有一步之遙了。他又被餵給 `landLeft (-1)`，這組合成了 `landLeft (-1) (1,4)`。由於失去了平衡，我們變得到了 `Nothing`。而我們把 `Nothing` 餵給 `landRight (-2)`，由於他是 `Nothing`，也就自動得到了 `Nothing`。

如果只把 `Maybe` 當作 applicative 用的話是沒有辦法達到我們要的效果的。你試著做一遍就會卡住。因為 applicative functor 並不允許 applicative value 之間有彈性的互動。他們最多就是讓我們可以用 applicative style 來傳遞參數給函數。applicative operators 能拿到他們的結果並把他用 applicative 的方式餵給另一個函數，並把最終的 applicative 值放在一起。但在每一步之間並沒有太多允許我們作手腳的機會。而我們的範例需要每一步都倚賴前一步的結果。當每一隻鳥降落的時候，我們都會把前一步的結果拿出來看看。好知道結果到底應該成功或失敗。

我們也能寫出一個函數，完全不管現在究竟有幾隻鳥停在竿子上，只是要害皮爾斯滑倒。我們可以稱呼這個函數叫做 `banana`：

```haskell
banana :: Pole -> Maybe Pole  
banana _ = Nothing
```

現在我們能把香蕉皮串到我們的過程中。他絕對會讓遇到的人滑倒。他完全不管前面的狀態是什麼都會產生失敗。

```haskell
ghci> return (0,0) >>= landLeft 1 >>= banana >>= landRight 1  
Nothing
```

`Just (1,0)` 被餵給 `banana`，而產生了 `Nothing`，之後所有的結果便都是 `Nothing` 了。

要同樣表示這種忽略前面的結果，只注重眼前的 monadic value 的情況，其實我們可以用 `>>` 來表達。

```haskell
(>>) :: (Monad m) => m a -> m b -> m b  
m >> n = m >>= \_ -> n
```

一般來講，碰到一個完全忽略前面狀態的函數，他就應該只會回傳他想回傳的值而已。但碰到 Monad，他們的 context 還是必須要被考慮到。來看一下 `>>` 串接 `Maybe` 的情況。

```haskell
ghci> Nothing >> Just 3  
Nothing  
ghci> Just 3 >> Just 4  
Just 4  
ghci> Just 3 >> Nothing  
Nothing
```

如果你把 `>>` 換成 `>>= \_ ->`，那就很容易看出他的意思。

我們也可以把 `banana` 改用 `>>` 跟 `Nothing` 來表達：

```haskell
ghci> return (0,0) >>= landLeft 1 >> Nothing >>= landRight 1  
Nothing
```

我們得到了保證的失敗。

我們也可以看看假如我們故意不用把 `Maybe` 視為有 context 的值的寫法。他會長得像這樣：

```haskell
routine :: Maybe Pole  
routine = case landLeft 1 (0,0) of  
    Nothing -> Nothing  
    Just pole1 -> case landRight 4 pole1 of   
            Nothing -> Nothing  
            Just pole2 -> case landLeft 2 pole2 of  
                    Nothing -> Nothing  
                    Just pole3 -> landLeft 1 pole3
```

![](/files/-LjUmqeGmJbp4A7Udw4r)

左邊先停了一隻鳥，然後我們停下來檢查有沒有失敗。當失敗的時候我們回傳 `Nothing`。當成功的時候，我們在右邊停一隻鳥，然後再重複前面做的事情。把這些瑣事轉換成 `>>=` 證明了 `Maybe` Monad 的力量，可以省去我們不少的時間。

注意到 `Maybe` 對 `>>=` 的實作，他其實就是在做碰到 `Nothing` 就會傳 `Nothing`，碰到正確值就繼續用 `Just` 傳遞值。

在這個章節中，我們看過了好幾個函數，也見識了用 `Maybe` monad 來表示失敗的 context 的力量。把普通的函數套用換成了 `>>=`，讓我們可以輕鬆地應付可能會失敗的情況，並幫我們傳遞 context。這邊的 context 就代表失敗的可能性，當我們套用函數到 context 的時候，就代表考慮進了失敗的情況。

## do 表示法

Monad 在 Haskell 中是十分重要的，所以我們還特別為了操作他設置了特別的語法：`do` 表示法。我們在介紹 I/O 的時候已經用過 `do` 來把小的 I/O action 串在一起了。其實 `do` 並不只是可以用在 `IO`，他可以用在任何 monad 上。他的原則是簡單明瞭，把 monadic value 串成一串。我們這邊來細看 `do` 是如何使用，以及為什麼我們十分倚賴他。

來看一下熟悉的例子：

```haskell
ghci> Just 3 >>= (\x -> Just (show x ++ "!"))  
Just "3!"
```

你說這沒什麼了不起，不過就是把 monadic value 餵給一個函數罷了。其中 `x` 就指定成 `3`。也從 monadic value 變成了普通值。那如果我們要在 lambda 中使用 `>>=` 呢？

```haskell
ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))  
Just "3!"
```

我們嵌一個 `>>=` 在另外一個 `>>=` 中。在外層的 lambda，我們把 `Just "!"` 餵給 `\y -> Just (show x ++ y)`。在內層的 lambda，`y` 被指定成 `"!"`。`x` 仍被指定成 `3`，是因為我們是從外層的 lambda 取值的。這些行為讓我們回想到下列式子：

```haskell
ghci> let x = 3; y = "!" in show x ++ y  
"3!"
```

差別在於前述的值是 monadic，具有失敗可能性的 context。我們可以把其中任何一步代換成失敗的狀態：

```haskell
ghci> Nothing >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))  
Nothing  
ghci> Just 3 >>= (\x -> Nothing >>= (\y -> Just (show x ++ y)))  
Nothing  
ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Nothing))  
Nothing
```

第一行中，把 `Nothing` 餵給一個函數，很自然地會回傳 `Nothing`。第二行裡，我們把 `Just 3` 餵給一個函數，所以 `x` 就成了 `3`。但我們把 `Nothing` 餵給內層的 lambda 所有的結果就成了 `Nothing`，這也進一步使得外層的 lambda 成了 `Nothing`。這就好比我們在 `let` expression 中來把值指定給變數一般。只差在我們這邊的值是 monadic value。

要再說得更清楚點，我們來把 script 改寫成每行都處理一個 `Maybe`：

```haskell
foo :: Maybe String  
foo = Just 3   >>= (\x -> 
      Just "!" >>= (\y -> 
      Just (show x ++ y)))
```

為了擺脫這些煩人的 lambda，Haskell 允許我們使用 `do` 表示法。他讓我們可以把先前的程式寫成這樣：

```haskell
foo :: Maybe String  
foo = do  
    x <- Just 3  
    y <- Just "!"  
    Just (show x ++ y)
```

![](/files/-LjUmqeIT7ufvbLOykVW)

這看起來好像讓我們不用在每一步都去檢查 `Maybe` 的值究竟是 `Just` 或 `Nothing`。這蠻方便的，如果在任何一個步驟我們取出了 `Nothing`。那整個 `do` 的結果就會是 `Nothing`。我們把整個責任都交給 `>>=`，他會幫我們處理所有 context 的問題。這邊的 `do` 表示法不過是另外一種語法的形式來串連所有的 monadic value 罷了。

在 `do` expression 中，每一行都是一個 monadic value。要檢查處理的結果的話，就要使用 `<-`。如果我們拿到一個 `Maybe String`，並用 `<-` 來綁定給一個變數，那個變數就會是一個 `String`，就像是使用 `>>=` 來將 monadic value 帶給 lambda 一樣。至於 `do` expression 中的最後一個值，好比說 `Just (show x ++ y)`，就不能用 `<-` 來綁定結果，因為那樣的寫法當轉換成 `>>=` 的結果時並不合理。他必須要是所有 monadic value 黏起來後的總結果，要考慮到前面所有可能失敗的情形。

舉例來說，來看看下面這行：

```haskell
ghci> Just 9 >>= (\x -> Just (x > 8))  
Just True
```

由於 `>>=` 左邊的參數是一個 `Just` 型態的值，當 lambda 被套用至 `9` 就會得到 `Just True`。如果我們重寫整個式子，改用 `do` 表示法：我們會得到：

```haskell
marySue :: Maybe Bool  
marySue = do   
    x <- Just 9  
    Just (x > 8)
```

如果我們比較這兩種寫法，就很容易看出為什麼整個 monadic value 的結果會是在 `do` 表示法中最後一個 monadic value 的值。他串連了全面所有的結果。

我們走鋼索的模擬程式也可以改用 `do` 表示法重寫。`landLeft` 跟 `landRight` 接受一個鳥的數字跟一個竿子來產生一個包在 `Just` 中新的竿子。而在失敗的情況會產生 `Nothing`。我們使用 `>>=` 來串連所有的步驟，每一步都倚賴前一步的結果，而且都帶有可能失敗的 context。這邊有一個範例，先是有兩隻鳥停在左邊，接著有兩隻鳥停在右邊，然後是一隻鳥停在左邊：

```haskell
routine :: Maybe Pole  
routine = do  
    start <- return (0,0)  
    first <- landLeft 2 start  
    second <- landRight 2 first  
    landLeft 1 second
```

我們來看看成功的結果：

```haskell
ghci> routine  
Just (3,2)
```

當我們要把這些 routine 用具體寫出的 `>>=`，我們會這樣寫：`return (0,0) >>= landLeft 2`，而有了 `do` 表示法，每一行都必須是一個 monadic value。所以我們清楚地把前一個 `Pole` 傳給 `landLeft` 跟 `landRight`。如果我們檢視我們綁定 `Maybe` 的變數，`start` 就是 `(0,0)`，而 `first` 就會是 `(2,0)`。

由於 `do` 表示法是一行一行寫，他們會看起來很像是命令式的寫法。但實際上他們只是代表序列而已，每一步的值都倚賴前一步的結果，並帶著他們的 context 繼續下去。

我們再重新來看看如果我們沒有善用 `Maybe` 的 monad 性質的程式：

```haskell
routine :: Maybe Pole  
    routine =   
        case Just (0,0) of   
            Nothing -> Nothing  
            Just start -> case landLeft 2 start of  
                Nothing -> Nothing  
                Just first -> case landRight 2 first of  
                    Nothing -> Nothing  
                    Just second -> landLeft 1 second
```

在成功的情形下，`Just (0,0)` 變成了 `start`， 而 `landLeft 2 start` 的結果成了 `first`。

如果我們想在 `do` 表示法裡面對皮爾斯丟出香蕉皮，我們可以這樣做：

```haskell
routine :: Maybe Pole  
routine = do  
    start <- return (0,0)  
    first <- landLeft 2 start  
    Nothing  
    second <- landRight 2 first  
    landLeft 1 second
```

當我們在 `do` 表示法寫了一行運算，但沒有用到 `<-` 來綁定值的話，其實實際上就是用了 `>>`，他會忽略掉計算的結果。我們只是要讓他們有序，而不是要他們的結果，而且他比寫成 `_ <- Nothing` 要來得漂亮的多。

你會問究竟我們何時要使用 `do` 表示法或是 `>>=`，這完全取決於你的習慣。在這個例子由於有每一步都倚賴於前一步結果的特性，所以我們使用 `>>=`。如果用 `do` 表示法，我們就必須清楚寫出鳥究竟是停在哪根竿子上，但其實每一次都是前一次的結果。不過他還是讓我們了解到怎麼使用 `do`。

在 `do` 表示法中，我們其實可以用模式匹配來綁定 monadic value，就好像我們在 `let` 表達式，跟函數參數中使用模式匹配一樣。這邊來看一個在 `do` 表示法中使用模式匹配的範例：

```haskell
justH :: Maybe Char  
justH = do  
    (x:xs) <- Just "hello"  
    return x
```

我們用模式匹配來取得 `"hello"` 的第一個字元，然後回傳結果。所以 `justH` 計算會得到 `Just 'h'`。

如果模式匹配失敗怎麼辦？當定義一個函數的時候，一個模式不匹配就會跳到下一個模式。如果所有都不匹配，那就會造成錯誤，整個程式就當掉。另一方面，如果在 `let` 中進行模式匹配失敗會直接造成錯誤。畢竟在 `let` 表達式的情況下並沒有失敗就跳下一個的設計。至於在 `do` 表示法中模式匹配失敗的話，那就會呼叫 `fail` 函數。他定義在 `Monad` 的 type class 定義豬。他允許在現在的 monad context 底下，失敗只會造成失敗而不會讓整個程式當掉。他預設的實作如下：

```haskell
fail :: (Monad m) => String -> m a  
fail msg = error msg
```

可見預設的實作的確是讓程式掛掉，但在某些考慮到失敗的可能性的 Monad（像是 `Maybe`）常常會有他們自己的實作。對於 `Maybe`，他的實作像是這樣：

```haskell
fail _ = Nothing
```

他忽略錯誤訊息，並直接回傳 `Nothing`。所以當在 `do` 表示法中的 `Maybe` 模式匹配失敗的時候，整個結果就會是 `Nothing`。這種方式比起讓程式掛掉要好多了。這邊來看一下 `Maybe` 模式匹配失敗的範例：

```haskell
wopwop :: Maybe Char  
wopwop = do  
    (x:xs) <- Just ""  
    return x
```

模式匹配的失敗，所以那一行的效果相當於一個 `Nothing`。我們來看看執行結果：

```haskell
ghci> wopwop  
Nothing
```

這樣模式匹配的失敗只會限制在我們 monad 的 context 中，而不是整個程式的失敗。這種處理方式要好多了。

## List Monad

![](/files/-LjUmqeW_F5mOxowt_dZ)

我們已經了解了 `Maybe` 可以被看作具有失敗可能性 context 的值，也見識到如何用 `>>=` 來把這些具有失敗考量的值傳給函數。在這一個章節中，我們要看一下如何利用 list 的 monadic 的性質來寫 non-deterministic 的程式。

我們已經討論過在把 list 當作 applicatives 的時候他們具有 non-deterministic 的性質。像 `5` 這樣一個值是 deterministic 的。他只有一種結果，而且我們清楚的知道他是什麼結果。另一方面，像 `[3,8,9]` 這樣的值包含好幾種結果，所以我們能把他看作是同時具有好幾種結果的值。把 list 當作 applicative functors 展示了這種特性：

```haskell
ghci> (*) <$> [1,2,3] <*> [10,100,1000]  
[10,100,1000,20,200,2000,30,300,3000]
```

將左邊 list 中的元素乘上右邊 list 中的元素這樣所有的組合全都被放進結果的 list 中。當處理 non-determinism 的時候，這代表我們有好幾種選擇可以選，我們也會每種選擇都試試看，因此最終的結果也會是一個 non-deterministic 的值。只是包含更多不同可能罷了。

non-determinism 這樣的 context 可以被漂亮地用 monad 來考慮。所以我們這就來看看 list 的 `Monad` instance 的定義：

```haskell
instance Monad [] where  
    return x = [x]  
    xs >>= f = concat (map f xs)  
    fail _ = []
```

`return` 跟 `pure` 是做同樣的事，所以我們應該算已經理解了 `return` 的部份。他接受一個值，並把他放進一個最小的一個 context 中。換種說法，就是他做了一個只包含一個元素的 list。這樣對於我們想要操作普通值的時候很有用，可以直接把他包起來變成 non-deterministic value。

要理解 `>>=` 在 list monad 的情形下是怎麼運作的，讓我們先來回歸基本。`>>=` 基本上就是接受一個有 context 的值，把他餵進一個只接受普通值的函數，並回傳一個具有 context 的值。如果操作的函數只會回傳普通值而不是具有 context 的值，那 `>>=` 在操作一次後就會失效，因為 context 不見了。讓我們來試著把一個 non-deterministic value 塞到一個函數中：

```haskell
ghci> [3,4,5] >>= \x -> [x,-x]  
[3,-3,4,-4,5,-5]
```

當我們對 `Maybe` 使用 `>>=`，是有考慮到可能失敗的 context。在這邊 `>>=` 則是有考慮到 non-determinism。`[3,4,5]` 是一個 non-deterministic value，我們把他餵給一個回傳 non-deterministic value 的函數。那結果也會是 non-deterministic。而且他包含了所有從 `[3,4,5]` 取值，套用 `\x -> [x,-x]` 後的結果。這個函數他接受一個數值並產生兩個數值，一個原來的數值與取過負號的數值。當我們用 `>>=` 來把一個 list 餵給這個函數，所有在 list 中的數值都保留了原有的跟取負號過的版本。`x` 會針對 list 中的每個元素走過一遍。

要看看結果是如何算出來的，只要看看實作就好了。首先我們從 `[3,4,5]` 開始。然後我們用 lambda 映射過所有元素得到：

```haskell
[[3,-3],[4,-4],[5,-5]]
```

lambda 會掃過每個元素，所以我們有一串包含一堆 list 的 list，最後我們在把這些 list 壓扁，得到一層的 list。這就是我們得到 non-deterministic value 的過程。

non-determinism 也有考慮到失敗的可能性。`[]` 其實等價於 `Nothing`，因為他什麼結果也沒有。所以失敗等同於回傳一個空的 list。所有的錯誤訊息都不用。讓我們來看看範例：

```haskell
ghci> [] >>= \x -> ["bad","mad","rad"]  
[]  
ghci> [1,2,3] >>= \x -> []  
[]
```

第一行裡面，一個空的 list 被丟給 lambda。因為 list 沒有任何元素，所以函數收不到任何東西而產生空的 list。這跟把 `Nothing` 餵給函數一樣。第二行中，每一個元素都被餵給函數，但所有元素都被丟掉，而只回傳一個空的 list。因為所有的元素都造成了失敗，所以整個結果也代表失敗。

就像 `Maybe` 一樣，我們可以用 `>>=` 把他們串起來：

```haskell
ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
```

![](/files/-LjUmqeYHTHd2nTdFTev)

`[1,2]` 被綁定到 `n` 而 `['a','b']` 被綁定到 `ch`。最後我們用 `return (n,ch)` 來把他放到一個最小的 context 中。在這個案例中，就是把 `(n,ch)` 放到 list 中，這代表最低程度的 non-determinism。整套結構要表達的意思就是對於 `[1,2]` 的每個元素，以及 `['a','b']` 的每個元素，我們產生一個 tuple，每項分別取自不同的 list。

一般來說，由於 `return` 接受一個值並放到最小的 context 中，他不會多做什麼額外的東西僅僅是展示出結果而已。

```
當你要處理 non-deterministic value 的時候，你可以把 list 中的每個元素想做計算路線的一個 branch。
```

這邊把先前的表達式用 `do` 重寫：

```haskell
listOfTuples :: [(Int,Char)]  
listOfTuples = do  
    n <- [1,2]  
    ch <- ['a','b']  
    return (n,ch)
```

這樣寫可以更清楚看到 `n` 走過 `[1,2]` 中的每一個值，而 `ch` 則取過 `['a','b']` 中的每個值。正如 `Maybe` 一般，我們從 monadic value 中取出普通值然後餵給函數。`>>=` 會幫我們處理好一切 context 相關的問題，只差在這邊的 context 指的是 non-determinism。

使用 `do` 來對 list 操作讓我們回想起之前看過的一些東西。來看看下列的片段：

```haskell
ghci> [ (n,ch) | n <- [1,2], ch <- ['a','b'] ]  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
```

沒錯，就是 list comprehension。在先前的範例中，`n` 會走過 `[1,2]` 的每個元素，而 `ch` 會走過 `['a','b']` 的每個元素。同時我們又把 `(n,ch)` 放進一個 context 中。這跟 list comprehension 的目的一樣，只是我們在 list comprehension 裡面不用在最後寫一個 `return` 來得到 `(n,ch)` 的結果。

實際上，list comprehension 不過是一個語法糖。不論是 list comprehension 或是用 `do` 表示法來表示，他都會轉換成用 `>>=` 來做計算。

List comprehension 允許我們 filter 我們的結果。舉例來說，我們可以只要包含 `7` 在表示位數裡面的數值。

```haskell
ghci> [ x | x <- [1..50], '7' `elem` show x ]  
[7,17,27,37,47]
```

我們用 `show` 跟 `x` 來把數值轉成字串，然後檢查 `'7'` 是否包含在字串裡面。要看看 filtering 要如何轉換成用 list monad 來表達，我們可以考慮使用 `guard` 函數，還有 `MonadPlus` 這個 type class。`MonadPlus` 這個 type class 是用來針對可以同時表現成 monoid 的 monad。下面是他的定義：

```haskell
class Monad m => MonadPlus m where  
    mzero :: m a  
    mplus :: m a -> m a -> m a
```

`mzero` 是其實是 `Monoid` 中 `mempty` 的同義詞，而 `mplus` 則對應到 `mappend`。因為 list 同時是 monoid 跟 monad，他們可以是 `MonadPlus` 的 instance。

```haskell
instance MonadPlus [] where  
    mzero = []  
    mplus = (++)
```

對於 list 而言，`mzero` 代表的是不產生任何結果的 non-deterministic value，也就是失敗的結果。而 `mplus` 則把兩個 non-deterministic value 結合成一個。`guard` 這個函數被定義成下列形式：

```haskell
guard :: (MonadPlus m) => Bool -> m ()  
guard True = return ()  
guard False = mzero
```

這函數接受一個布林值，如果他是 `True` 就回傳一個包在預設 context 中的 `()`。如果他失敗就產生 mzero。

```haskell
ghci> guard (5 > 2) :: Maybe ()  
Just ()  
ghci> guard (1 > 2) :: Maybe ()  
Nothing  
ghci> guard (5 > 2) :: [()]  
[()]  
ghci> guard (1 > 2) :: [()]  
[]
```

看起來蠻有趣的，但用起來如何呢？我們可以用他來過濾 non-deterministic 的計算。

```haskell
ghci> [1..50] >>= (\x -> guard ('7' `elem` show x) >> return x)  
[7,17,27,37,47]
```

這邊的結果跟我們之前 list comprehension 的結果一致。究竟 `guard` 是如何辦到的？我們先看看 `guard` 跟 `>>` 是如何互動：

```haskell
ghci> guard (5 > 2) >> return "cool" :: [String]  
["cool"]  
ghci> guard (1 > 2) >> return "cool" :: [String]  
[]
```

如果 `guard` 成功的話，結果就會是一個空的 tuple。接著我們用 `>>` 來忽略掉空的 tuple，而呈現不同的結果。另一方面，如果 `guard` 失敗的話，後面的 `return` 也會失敗。這是因為用 `>>=` 把空的 list 餵給函數總是會回傳空的 list。基本上 `guard` 的意思就是：如果一個布林值是 `False` 那就產生一個失敗狀態，不然的話就回傳一個基本的 `()`。這樣計算就可以繼續進行。

這邊我們把先前的範例用 `do` 改寫：

```haskell
sevensOnly :: [Int]  
sevensOnly = do  
    x <- [1..50]  
    guard ('7' `elem` show x)  
    return x
```

如果我們不寫最後一行 `return x`，那整個 list 就會是包含一堆空 tuple 的 list。

把上述範例寫成 list comprehension 的話就會像這樣：

```haskell
ghci> [ x | x <- [1..50], '7' `elem` show x ]  
[7,17,27,37,47]
```

所以 list comprehension 的 filtering 基本上跟 `guard` 是一致的。

### A knight's quest

這邊來看一個可以用 non-determinism 解決的問題。假設你有一個西洋棋盤跟一隻西洋棋中的騎士擺在上面。我們希望知道是否這隻騎士可以在三步之內移到我們想要的位置。我們只要用一對數值來表示騎士在棋盤上的位置。第一個數值代表棋盤的行，而第二個數值代表棋盤的列。

![](/files/-LjUmqebdkAKEpwqb4O_)

我們先幫騎士的位置定義一個 type synonym。

```haskell
type KnightPos = (Int,Int)
```

假設騎士現在是在 `(6,2)`。究竟他能不能夠在三步內移動到 `(6,1)` 呢？你可能會先考慮究竟哪一步是最佳的一步。但不如全部一起考慮吧！要好好利用所謂的 non-determinism。所以我們不是只選擇一步，而是選擇全部。我們先寫一個函數回傳所有可能的下一步：

```haskell
moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = do  
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
                ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
                ]  
    guard (c' `elem` [1..8] && r' `elem` [1..8])
    return (c',r')
```

騎士有可能水平或垂直移動一步或二步，但問題是他們必須要同時水平跟垂直移動。`(c',r')` 走過 list 中的每一個元素，而 `guard` 會保證產生的結果會停留在棋盤上。如果沒有，那就會產生一個空的 list，表示失敗的結果，`return (c',r')` 也就不會被執行。

這個函數也可以不用 list monad 來寫，但我們這邊只是寫好玩的。下面是一個用 `filter` 實現的版本：

```haskell
moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = filter onBoard  
    [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
    ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
    ]  
    where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]
```

兩個函數做的都是相同的事，所以選個你喜歡的吧。

```haskell
ghci> moveKnight (6,2)  
[(8,1),(8,3),(4,1),(4,3),(7,4),(5,4)]  
ghci> moveKnight (8,1)  
[(6,2),(7,3)]
```

我們接受一個位置然後產生所有可能的移動方式。所以我們有一個 non-deterministic 的下一個位置。我們用 `>>=` 來餵給 `moveKnight`。接下來我們就可以寫一個三步內可以達到的所有位置：

```haskell
in3 :: KnightPos -> [KnightPos]  
in3 start = do   
    first <- moveKnight start  
    second <- moveKnight first  
    moveKnight second
```

如果你傳 `(6,2)`，得到的 list 會很大，因為會有不同種方式來走到同樣的一個位置。我們也可以不用 `do` 來寫：

```haskell
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
```

第一次 `>>=` 給我們移動一步的所有結果，第二次 `>>=` 給我們移動兩步的所有結果，第三次則給我們移動三步的所有結果。

用 `return` 來把一個值放進預設的 context 然後用 `>>=` 餵給一個函數其實跟函數呼叫是同樣的，只是用不同的寫法而已。 接著我們寫一個函數接受兩個位置，然後可以測試是否可以在三步內從一個位置移到另一個位置：

```haskell
canReachIn3 :: KnightPos -> KnightPos -> Bool  
canReachIn3 start end = end `elem` in3 start
```

我們產生所有三步的可能位置，然後看看其中一個位置是否在裡面。所以我們可以看看是否可以在三步內從 `(6,2)` 走到 `(6,1)`：

```haskell
ghci> (6,2) `canReachIn3` (6,1)  
True
```

那從 `(6,2)` 到 `(7,3)` 呢？

```haskell
ghci> (6,2) `canReachIn3` (7,3)  
False
```

答案是不行。你可以修改函數改成當可以走到的時候，他還會告訴你實際的步驟。之後你也可以改成不只限定成三步，可以任意步。

## Monad laws (單子律)

![](/files/-LjUmqed5emo1wTpEvWf)

正如 applicative functors 以及 functors，Monad 也有一些要遵守的定律。我們定義一個 `Monad` 的 instance 並不代表他是一個 monad，只代表他被定義成那個 type class 的 instance。一個型態要是 monad，則必須遵守單子律。這些定律讓我們可以對這個型態的行為做一些合理的假設。

Haskell 允許任何型態是任何 type class 的 instance。但他不會檢查單子律是否有被遵守，所以如果我們要寫一個 `Monad` 的 instance，那最好我們確定他有遵守單子律。我們可以不用擔心標準函式庫中的型態是否有遵守單子律。但之後我們定義自己的型態時，我們必須自己檢查是否有遵守單子律。不用擔心，他們不會很複雜。

### Left identity

單子律的第一項說當我們接受一個值，將他用 `return` 放進一個預設的 context 並把他用 `>>=` 餵進一個函數的結果，應該要跟我們直接做函數呼叫的結果一樣。

* `retrun x >>= f` 應該等於 `f x`

如果你是把 monadic value 視為把一個值放進最小的 context 中，僅僅是把同樣的值放進結果中的話， 那這個定律應該很直覺。因為把這個值放進 context 中然後丟給函數，應該要跟直接把這個值丟給函數做呼叫應該沒有差別。

對於 `Maybe` monad，`return` 被定義成 `Just`。`Maybe` monad 講的是失敗的可能性，如果我們有普通值要把他放進 context 中，那把這個動作當作是計算成功應該是很合理的，畢竟我們都知道那個值是很具體的。這邊有些範例：

```haskell
ghci> return 3 >>= (\x -> Just (x+100000))  
Just 100003  
ghci> (\x -> Just (x+100000)) 3  
Just 100003
```

對於 list monad 而言，`return` 是把值放進一個 list 中，變成只有一個元素的 list。`>>=` 則會走過 list 中的每個元素，並把他們丟給函數做運算，但因為在單一元素的 list 中只有一個值，所以跟直接對那元素做運算是等價的：

```haskell
ghci> return "WoM" >>= (\x -> [x,x,x])  
["WoM","WoM","WoM"]  
ghci> (\x -> [x,x,x]) "WoM"  
["WoM","WoM","WoM"]
```

至於 `IO`，我們已經知道 `return` 並不會造成副作用，只不過是在結果中呈現原有值。所以這個定律對於 `IO` 也是有效的。

### Right identity

單子律的第二個規則是如果我們有一個 monadic value，而且我們把他用 `>>=` 餵給 `return`，那結果就會是原有的 monadic value。

* `m >>= return` 會等於 `m`

這一個可能不像第一定律那麼明顯，但我們還是來看看為什麼會遵守這條。當我們把一個 monadic value 用 `>>=` 餵給函數，那些函數是接受普通值並回傳具有 context 的值。`return` 也是在他們其中。如果你仔細看他的型態，`return` 是把一個普通值放進一個最小 context 中。這就表示，對於 `Maybe` 他並沒有造成任何失敗的狀態，而對於 list 他也沒有多加 non-determinism。

```haskell
ghci> Just "move on up" >>= (\x -> return x)  
Just "move on up"  
ghci> [1,2,3,4] >>= (\x -> return x)  
[1,2,3,4]  
ghci> putStrLn "Wah!" >>= (\x -> return x)  
Wah!
```

如果我們仔細檢視 list monad 的範例，會發現 `>>=` 的實作是：

```haskell
xs >>= f = concat (map f xs)
```

所以當我們將 `[1,2,3,4]` 丟給 `return`，第一個 `return` 會把 `[1,2,3,4]` 映射成 `[[1],[2],[3],[4]]`，然後再把這些小 list 串接成我們原有的 list。

Left identity 跟 right identity 是描述 `return` 的行為。他重要的原因是因為他把普通值轉換成具有 context 的值，如果他出錯的話會很頭大。

### Associativity

單子律最後一條是說當我們用 `>>=` 把一串 monadic function 串在一起，他們的先後順序不應該影響結果：

* `(m >>= f) >>= g` 跟 `m >>= (\x -> f x >>= g)` 是相等的

究竟這邊說的是什麼呢？我們有一個 monadic value `m`，以及兩個 monadic function `f` 跟 `g`。當我們寫下 `(m >>= f) >>= g`，代表的是我們把 `m` 餵給 `f`，他的結果是一個 monadic value。然後我們把這個結果餵給 `g`。而在 `m >>= (\x -> f x >>= g)` 中，我們接受一個 monadic value 然後餵給一個函數，這個函數會把 `f x` 的結果丟給 `g`。我們不太容易直接看出兩者相同，所以先來看個範例比較好理解。

還記得之前皮爾斯的範例嗎？要模擬鳥停在他的平衡竿上，我們把好幾個函數串在一起

```haskell
ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2  
Just (2,4)
```

從 `Just (0,0)` 出發，然後把值傳給 `landRight 2`。他的結果又被綁到下一個 monadic function，以此類推。如果我們用括號清楚標出優先順序的話會是這樣：

```haskell
ghci> ((return (0,0) >>= landRight 2) >>= landLeft 2) >>= landRight 2  
Just (2,4)
```

我們也可以改寫成這樣：

```haskell
return (0,0) >>= (\x -> 
landRight 2 x >>= (\y -> 
landLeft 2 y >>= (\z -> 
landRight 2 z)))
```

`return (0,0)` 等價於 `Just (0,0)`，當我們把他餵給 lambda，裡面的 `x` 就等於 `(0,0)`。`landRight` 接受一個數值跟 pole，算出來的結果是 `Just (0,2)` 然後把他餵給另一個 lambda，裡面的 `y` 就變成了 `(0,2)`。這樣的操作持續下去，直到最後一隻鳥降落，而得到 `Just (2,4)` 的結果，這也是整個操作的總結果。

這些 monadic function 的優先順序並不重要，重點是他們的意義。從另一個角度來看這個定律：考慮兩個函數 `f` 跟 `g`，將兩個函數組合起來的定義像是這樣：

```haskell
(.) :: (b -> c) -> (a -> b) -> (a -> c)  
f . g = (\x -> f (g x))
```

如果 `g` 的型態是 `a -> b` 且 `f` 的型態是 `b -> c`，我們可以把他們合成一個型態是 `a -> c` 的新函數。所以中間的參數都有自動帶過。現在假設這兩個函數是 monadic function，也就是說如果他們的回傳值是 monadic function？如果我們有一個函數他的型態是 `a -> m b`，我們並不能直接把結果丟給另一個型態為 `b -> m c` 的函數，因為後者只接受型態為 `b` 的普通值。然而，我們可以用 `>>=` 來做到我們想要的事。有了 `>>=`，我們可以合成兩個 monadic function：

```haskell
(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)  
f <=< g = (\x -> g x >>= f)
```

所以現在我們可以合成兩個 monadic functions：

```haskell
ghci> let f x = [x,-x]  
ghci> let g x = [x*3,x*2]  
ghci> let h = f <=< g  
ghci> h 3  
[9,-9,6,-6]
```

至於這跟結合律有什麼關係呢？當我們把這定律看作是合成的定律，他就只是說了 `f <=< (g <=< h)` 跟 `(f <=< g) <=< h` 應該等價。只是他是針對 monad 而已。

如果我們把頭兩個單子律用 `<=<` 改寫，那 left identity 不過就是說對於每個 monadic function `f`，`f <=< return` 跟 `f` 是等價，而 right identity 說 `return <=< f` 跟 `f` 是等價。

如果看看普通函數的情形，就會發現很像，`(f . g) . h` 等價於 `f . (g . h)`，`f . id` 跟 `f` 等價，且 `id . f` 等價於 `f`。

在這一章中，我們檢視了 monad 的基本性質，而且也了解了 `Maybe` monad 跟 list monad 的運作方式。在下一章，我們會看看其他一些有特色的 monad，我們也會學到如何定義自己的 monad。


# ch13


# 再來看看更多 Monad

![](/files/-LjUms--1DE6snwyA7rN)

我們已經看過 Monad 是如何接受具有 context 的值，並如何用函數操作他們 還有如何用 `>>=` 跟 `do` 來減輕我們對 context 的關注，集中精神在 value 本身。

我們也看過了 `Maybe` 是如何把值加上一個可能會失敗的 context。我們學習到 List Monad 是如何加進多重結果的 context。我們也了解 `IO` Monad 如何運作，而且我們在知道什麼是 Monad 之前就已經知道他了。

在這個章節，我們會介紹一些其他的 Monad。他們可以把值變成 monadic value，因此可以讓我們的程式更簡潔清晰。多見識幾個 Monad 也可以敏銳我們對 Monad 的直覺。

我們即將要介紹的 Monad 都包含在 `mtl` 這個套建中。一個 Haskell package 包含了一堆模組。而 `mtl` 已經包含在 Haskell Platform 中，所以你可能不用另外安裝。要檢查你有沒有這套件，你可以下 `ghc-pkg list`。這會列出你已經安裝的套件，其中應該包含 `mtl` 後面接著對應的版號。

## 你所不知道的 Writer Monad

我們已經看過 `Maybe`, list 以及 `IO` Monad。現在我們要來看看 `Writer` Monad。

相對於 `Maybe` 是加入可能失敗的 context，list 是加入 non-deterministic 的 context，`Writer` 則是加進一個附加值的 context，好比 log 一般。`Writer` 可以讓我們在計算的同時蒐集所有 log 紀錄，並匯集成一個 log 並附加在結果上。

例如我們想要附加一個 String 好說明我們的值在幹麼（有可能是為了除錯）。想像有一個函數接受一個代表幫派人數的數字，然後會回傳值告訴我們這是否算是一個龐大的幫派：

```haskell
isBigGang :: Int -> Bool  
isBigGang x = x > 9
```

現在我們希望他不只是回傳 `True` 或 `False`，我們還希望他能夠多回傳一個字串代表 log。這很容易，只要多加一個 `String` 在 `Bool` 旁邊就好了。

```haskell
isBigGang :: Int -> (Bool, String)  
isBigGang x = (x > 9, "Compared gang size to 9.")
```

我們現在回傳了一個 Tuple，第一個元素是原來的布林值，第二個元素是一個 String。現在我們的值有了一個 context。

```haskell
ghci> isBigGang 3  
(False,"Compared gang size to 9.")  
ghci> isBigGang 30  
(True,"Compared gang size to 9.")
```

![](/files/-LjUms-3fyZvUDnce_2w)

到目前為止都還不錯，`isBigGang` 回傳一個值跟他的 context。對於正常的數值來說這樣的寫法都能運作良好。但如果我們想要把一個已經具有 context 的值，像是 `(3, "Smallish gang.")`，餵給 `isBigGang` 呢？我們又面對了同樣的問題：如果我們有一個能接受正常數值並回傳一個具有 context 值的 function，那我們要如何餵給他一個具有 context 的值？

當我們在研究 `Maybe` monad 的時候，我們寫了一個 `applyMaybe`。他接受一個 `Maybe a` 值跟一個 `a -> Maybe b` 型態的函數，他會把 `Maybe a` 餵給這個 function，即便這個 function 其實是接受 `a` 而非 `Maybe a`。`applyMaybe` 有針對這樣的 context 做處理，也就是會留意有可能發生的失敗情況。但在 `a -> Maybe b` 裡面，我們可以只專心處理正常數值即可。因為 `applyMaybe` (之後變成了 `>>=`)會幫我們處理需要檢查 `Nothing` 或 `Just` 的情況。

我們再來寫一個接受附加 log 值的函數，也就是 `(a, String)` 型態的值跟 `a -> (b, String)` 型態的函數。我們稱呼這個函數為 `applyLog`。這個函數有的 context 是附加 log 值，而不是一個可能會失敗的 context，因此 `applyLog` 會確保原有的 log 被保留，並附上從函數產生出的新的 log。這邊我們來看一下實作：

```haskell
applyLog :: (a,String) -> (a -> (b,String)) -> (b,String)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log ++ newLog)
```

當我們想把一個具有 context 的值餵給一個函數的時候，我們會嘗試把值跟他的 context 分開，然後把值餵給函數再重新接回 context。在 `Maybe` monad 的情況，我們檢查值是否為 `Just x`，如果是，便將 `x` 餵給函數。而在 log 的情況，我們知道 pair 的其中一個 component 是 log 而另一個是值。所以我們先取出值 `x`，將 `f` apply 到 `x`，便獲取 `(y,newLog)`，其中 `y` 是新的值而 `newLog` 則是新的 log。但如果我們回傳 `newLog`，舊的 log 便不會包含進去，所以我們要回傳的是 `(y, log ++ newLog)`。我們用 `++` 來把新的 log 接到舊的上面。

來看看 `applyLog` 運作的情形：

```haskell
ghci> (3, "Smallish gang.") `applyLog` isBigGang  
(False,"Smallish gang.Compared gang size to 9")  
ghci> (30, "A freaking platoon.") `applyLog` isBigGang  
(True,"A freaking platoon.Compared gang size to 9")
```

跟之前的結果很像，只差在我們多了伴隨產生的 log。再來多看幾個例子：

```haskell
ghci> ("Tobin","Got outlaw name.") `applyLog` (\x -> (length x, "Applied length."))  
(5,"Got outlaw name.Applied length.")  
ghci> ("Bathcat","Got outlaw name.") `applyLog` (\x -> (length x, "Applied length"))  
(7,"Got outlaw name.Applied length")
```

可以看到在 lambda 裡面 `x` 只是個正常的字串而不是 tuple，且 `applyLog` 幫我們處理掉附加 log 的動作。

### Monoids 的好處

```
請確定你了解什麼是 Monoids。
```

到目前為止 `applyLog` 接受 `(a,String)` 型態的值，但為什麼 log 一定要是 `String` 呢？我們使用 `++` 來附加新的 log，難道 `++` 並不能運作在任何形式的 list，而一定要限制我們在 `String` 上呢？我們當然可以擺脫 `String`，我們可以如下改變他的型態：

```haskell
applyLog :: (a,[c]) -> (a -> (b,[c])) -> (b,[c])
```

我們用一個 List 來代表 Log。包含在 List 中的元素型態必須跟原有的 List 跟回傳的 List 型態相同，否則我們沒辦法用 `++` 來把他們接起來。

這能夠運作在 bytestring 上嗎？絕對沒問題。只是我們現在的型態只對 List 有效。我們必須要另外做一個 bytestring 版本的 `applyLog`。但我們注意到 List 跟 bytestring 都是 monoids。因此他們都是 `Monoid` type class 的 instance，那代表他們都有實作 `mappend`。對 List 以及 bytestring 而言，`mappend` 都是拿來串接的。

```haskell
ghci> [1,2,3] `mappend` [4,5,6]  
[1,2,3,4,5,6]  
ghci> B.pack [99,104,105] `mappend` B.pack [104,117,97,104,117,97]  
Chunk "chi" (Chunk "huahua" Empty)
```

修改後我們的 `applyLog` 可以運作在任何 monoid 上。我們必須要修改型態宣告來表示這件事，同時也要在實作中把 `++` 改成 `mappend`：

```haskell
applyLog :: (Monoid m) => (a,m) -> (a -> (b,m)) -> (b,m)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log `mappend` newLog)
```

由於現在包含的值可以是任何 monoid，我們不再需要把 tuple 想成包含一個值跟對應的 log，我們可以想成他包含一個值跟一個對應的 monoid。舉例來說，可以說我們有一個 tuple 包含一個產品名稱跟一個符合 monoid 特性的產品價格。我們可以定義一個 `Sum` 的 newtype 來保證我們在操作產品的時候也會把價錢跟著加起來。

```haskell
import Data.Monoid  

type Food = String  
type Price = Sum Int  

addDrink :: Food -> (Food,Price)  
addDrink "beans" = ("milk", Sum 25)  
addDrink "jerky" = ("whiskey", Sum 99)  
addDrink _ = ("beer", Sum 30)
```

我們用 string 來代表食物，用 `newtype` 重新定義 `nInt` 為 `Sum`，來追蹤總共需要花多少錢。可以注意到我們用 `mappend` 來操作 `Sum` 的時候，價錢會被一起加起來。

```haskell
ghci> Sum 3 `mappend` Sum 9  
Sum {getSum = 12}
```

`addDrink` 的實作很簡單，如果我們想吃豆子，他會回傳 `"milk"` 以及伴隨的 `Sum 25`，同樣的如果我們要吃 "jerky"，他就會回傳 "whiskey"，要吃其他東西的話，就會回傳 "beer"。乍看之下這個函數沒什麼特別，但如果用 `applyLog` 的話就會有趣些。

```haskell
ghci> ("beans", Sum 10) `applyLog` addDrink  
("milk",Sum {getSum = 35})  
ghci> ("jerky", Sum 25) `applyLog` addDrink  
("whiskey",Sum {getSum = 124})  
ghci> ("dogmeat", Sum 5) `applyLog` addDrink  
("beer",Sum {getSum = 35})
```

牛奶價值 `25` 美分，但如果我們也吃了價值 `10` 美分的豆子的話，總共需要付 `35` 美分。這樣很清楚地展示了伴隨的值不一定需要是 log，他可以是任何 monoid。至於兩個值要如何結合，那要看 monoid 中怎麼定義。當我們需要的是 log 的時候，他們是串接，但這個 case 裡面，數字是被加起來。

由於 `addDrink` 回傳一個 `(Food,Price)`，我們可以再把結果重新餵給 `addDrink`，這可以很容易告訴我們總共喝了多少錢：

```haskell
ghci> ("dogmeat", Sum 5) `applyLog` addDrink `applyLog` addDrink  
("beer",Sum {getSum = 65})
```

將狗食跟 30 美分的啤酒加在一起會得到 `("beer", Sum 35)`。如果我們用 `applyLog` 將上面的結果再餵給 `addDrink`，我們會得到 `("beer", Sum 65)` 這樣的結果。

### The Writer type

我們認識了一個附加 monoid 的值其實表現出來的是一個 monad，我們來再來看看其他類似的 `Monad` instance。`Control.Monad.Writer` 這模組含有 `Writer w a` 的一個型態，裏面定義了他 `Monad` 的 instance，還有一些操作這些值的函數。

首先，我們來看一下型態。要把一個 monoid 附加給一個值，只需要定義一個 tuple 就好了。`Writer w a` 這型態其實是一個 `newtype` wrapper。他的定義很簡單：

```haskell
newtype Writer w a = Writer { runWriter :: (a, w) }
```

他包在一個 `newtype` 裏面，並且可以是一個 `Monad` 的 instance，而且這樣定義的好處是可以跟單純 tuple 的型態區分開來。`a` 這個型態參數代表是包含的值的型態，而 `w` 則是附加的 monoid 的型態。

他 `Monad` instance 的定義如下：

```haskell
instance (Monoid w) => Monad (Writer w) where  
    return x = Writer (x, mempty)  
    (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
```

![](/files/-LjUms-B3tnXU6aJKydc)

首先，我們來看看 `>>=`。他的實作基本上就是 `applyLog`，只是我們的 tuple 現在是包在一個 `Writer` 的 `newtype` 中，我們可以用 pattern matching 的方式把他給 unwrap。我們將 `x` 餵給 `f`。這會回給我們 `Writer w a`。接著可以用 `let` expression 來做 pattern matching。把結果綁定到 `y` 這個名字上，然後用 `mappend` 來結合舊的 monoid 值跟新的 monoid 值。最後把結果跟 monoid 值用 `Writer` constructor 包起來，形成我們最後的 `Writer` value。

那 `return` 呢？回想 `return` 的作用是接受一個值，並回傳一個具有意義的最小 context 來裝我們的值。那究竟什麼樣的 context 可以代表我們的 `Writer` 呢？如果我們希望 monoid 值所造成的影響愈小愈好，那 `mempty` 是個合理的選擇。`mempty` 是被當作 identity monoid value，像是 `""` 或 `Sum 0`，或是空的 bytestring。當我們對 `mempty` 用 `mappend` 跟其他 monoid 值結合，結果會是其他的 monoid 值。所以如果我們用 `return` 來做一個 `Writer`，然後用 `>>=` 來餵給其他的函數，那函數回傳的便是算出來的 monoid。下面我們試著用 `return` 搭配不同 context 來回傳 `3`：

```haskell
ghci> runWriter (return 3 :: Writer String Int)  
(3,"")  
ghci> runWriter (return 3 :: Writer (Sum Int) Int)  
(3,Sum {getSum = 0})  
ghci> runWriter (return 3 :: Writer (Product Int) Int)  
(3,Product {getProduct = 1})
```

因為 `Writer` 並沒有定義成 `Show` 的 instance，我們必須用 `runWriter` 來把我們的 `Writer` 轉成正常的 tuple。對於 `String`，monoid 的值就是空字串。而對於 `Sum` 來說則是 `0`，因為 `0` 加上其他任何值都會是對方。而對 `Product` 來說，則是 `1`。

這裡的 `Writer` instance 並沒有定義 `fail`，所以如果 pattern matching 失敗的話，就會呼叫 `error`。

### Using do notation with Writer

既然我們定義了 `Monad` 的 instance，我們自然可以用 `do` 串接 `Writer` 型態的值。這在我們需要對一群 `Writer` 型態的值做處理時顯得特別方便。就如其他的 monad，我們可以把他們當作具有 context 的值。在現在這個 case 中，所有的 monoid 的值都會用 `mappend` 來連接起來並得到最後的結果。這邊有一個簡單的範例，我們用 `Writer` 來相乘兩個數。

```haskell
import Control.Monad.Writer  

logNumber :: Int -> Writer [String] Int  
logNumber x = Writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b)
```

`logNumber` 接受一個數並把這個數做成一個 `Writer`。我們再用一串 string 來當作我們的 monoid 值，每一個數都跟著一個只有一個元素的 list，說明我們只有一個數。`multWithLog` 式一個 `Writer`，他將 `3` 跟 `5` 相乘並確保相乘的紀錄有寫進最後的 log 中。我們用 `return` 來做成 `a*b` 的結果。我們知道 `return` 會接受某個值並加上某個最小的 context，我們可以確定他不會多添加額外的 log。如果我們執行程式會得到：

```haskell
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5"])
```

有時候我們就是想要在某個時間點放進某個 Monoid value。`tell` 正是我們需要的函數。他實作了 `MonadWriter` 這個 type class，而且在當 `Writer` 用的時候也能接受一個 monoid value，好比說 `["This is going on"]`。我們能用他來把我們的 monoid value 接到任何一個 dummy value `()` 上來形成一個 Writer。當我們拿到的結果是 `()` 的時候，我們不會把他綁定到變數上。來看一個 `multWithLog` 的範例：

```haskell
multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    tell ["Gonna multiply these two"]  
    return (a*b)
```

`return (a*b)` 是我們的最後一行，還記得在一個 `do` 中的最後一行代表整個 `do` 的結果。如果我們把 `tell` 擺到最後，則 `do` 的結果則會是 `()`。我們會因此丟掉乘法運算的結果。除此之外，log 的結果是不變的。

```haskell
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])
```

### Adding logging to programs

歐幾里得算法是找出兩個數的最大公因數。Haskell 已經提供了 `gcd` 的函數，但我們來實作一個具有 log 功能的 gcd：

```haskell
gcd' :: Int -> Int -> Int  
gcd' a b   
    | b == 0    = a  
    | otherwise = gcd' b (a `mod` b)
```

演算法的內容很簡單。首先他檢查第二個數字是否為零。如果是零，那就回傳第一個數字。如果不是，那結果就是第二個數字跟將第一個數字除以第二個數字的餘數兩個數字的最大公因數。舉例來說，如果我們想知道 8 跟 3 的最大公因數，首先可以注意到 3 不是 0。所以我們要求的是 3 跟 2 的最大公因數(8 除以 3 餘二)。接下去我可以看到 2 不是 0，所以我們要再找 2 跟 1 的最大公因數。同樣的，第二個數不是 0，所以我們再找 1 跟 0 的最大公因數。最後第二個數終於是 0 了，所以我們得到最大公因數是 1。

```haskell
ghci> gcd' 8 3  
1
```

答案真的是這樣。接著我們想加進 context，context 會是一個 monoid value 並且像是一個 log 一樣。就像之前的範例，我們用一串 string 來當作我們的 monoid。所以 `gcd'` 會長成這樣：

```haskell
gcd' :: Int -> Int -> Writer [String] Int
```

而他的程式碼會像這樣：

```haskell
import Control.Monad.Writer  

gcd' :: Int -> Int -> Writer [String] Int  
gcd' a b  
  | b == 0 = do  
      tell ["Finished with " ++ show a]  
      return a  
  | otherwise = do  
      tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
      gcd' b (a `mod` b)
```

這個函數接受兩個 `Int` 並回傳一個 `Writer [String] Int`，也就是說是一個有 log context 的 `Int`。當 `b` 等於 `0` 的時候，我們用一個 `do` 來組成一個 `Writer` 的值。我們先用 `tell` 來寫入我們的 log，然後用 `return` 來當作 `do` 的結果。當然我們也可以這樣寫：

```haskell
Writer (a, ["Finished with " ++ show a])
```

但我想 `do` 的表達方式是比較容易閱讀的。接下來我們看看當 `b` 不等於 `0` 的時候。我們會把 `mod` 的使用情況寫進 log。然後在 `do` 當中的第二行遞迴呼叫 `gcd'`。`gcd'` 現在是回傳一個 `Writer` 的型態，所以 ``gcd' b (a `mod` b)`` 這樣的寫法是完全沒問題的。

儘管去 trace 這個 `gcd'` 對於理解十分有幫助，但我想了解整個大概念，把值視為具有 context 是更加有用的。

接著來試試跑我們的 `gcd'`，他的結果會是 `Writer [String] Int`，如果我們把他從 `newtype` 中取出來，我們會拿到一個 tuple。tuple 的第一個部份就是我們要的結果：

```haskell
ghci> fst $ runWriter (gcd' 8 3)  
1
```

至於 log 呢，由於 log 是一連串 string，我們就用 `mapM_ putStrLn` 來把這些 string 印出來：

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter (gcd' 8 3)  
8 mod 3 = 2  
3 mod 2 = 1  
2 mod 1 = 0  
Finished with 1
```

把普通的演算法轉換成具有 log 是很棒的經驗，我們不過是把普通的 value 重寫成 Monadic value，剩下的就靠 `>>=` 跟 `Writer` 來幫我們處理一切。用這樣的方法我們幾乎可以對任何函數加上 logging 的功能。我們只要把普通的值換成 `Writer`，然後把一般的函數呼叫換成 `>>=` (當然也可以用 `do`)

### Inefficient list construction

當製作 `Writer` Monad 的時候，要特別注意你是使用哪種 monoid。使用 list 的話效能有時候是沒辦法接受的。因為 list 是使用 `++` 來作為 `mappend` 的實現。而 `++` 在 list 很長的時候是非常慢的。

在之前的 `gcd'` 中，log 並不會慢是因為 list append 的動作實際上看起來是這樣：

```haskell
a ++ (b ++ (c ++ (d ++ (e ++ f))))
```

list 是建立的方向是從左到右，當我們先建立左邊的部份，而把另一串 list 加到右邊的時候效能會不錯。但如果我們不小心使用，而讓 `Writer` monad 實際在操作 list 的時候變成像這樣的話。

```haskell
((((a ++ b) ++ c) ++ d) ++ e) ++ f
```

這會讓我們的操作是 left associative，而不是 right associative。這非常沒有效率，因為每次都是把右邊的部份加到左邊的部份，而左邊的部份又必須要從頭開始建起。

下面這個函數跟 `gcd'` 差不多，只是 log 的順序是相反的。他先紀錄剩下的操作，然後紀錄現在的步驟。

```haskell
import Control.Monad.Writer  

gcdReverse :: Int -> Int -> Writer [String] Int  
gcdReverse a b  
  | b == 0 = do  
      tell ["Finished with " ++ show a]  
      return a  
    | otherwise = do  
      result <- gcdReverse b (a `mod` b)  
      tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
      return result
```

他先遞迴呼叫，然後把結果綁定到 `result`。然後把目前的動作寫到 log，在遞迴的結果之後。最後呈現的就是完整的 log。

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter (gcdReverse 8 3)  
Finished with 1  
2 mod 1 = 0  
3 mod 2 = 1  
8 mod 3 = 2
```

這沒效率是因為他讓 `++` 成為 left associative 而不是 right associative。

### Difference lists

![](/files/-LjUms-Frhn1g-o8B1jC)

由於 list 在重複 append 的時候顯得低效，我們最好能使用一種支援高效 appending 的資料結構。其中一種就是 difference list。difference list 很類似 list，只是他是一個函數。他接受一個 list 並 prepend 另一串 list 到他前面。一個等價於 `[1,2,3]` 的 difference list 是這樣一個函數 `\xs -> [1,2,3] ++ xs`。一個等價於 `[]` 的 difference list 則是 `\xs -> [] ++ xs`。

Difference list 最酷的地方在於他支援高效的 appending。當我們用 `++` 來實現 appending 的時候，他必須要走到左邊的 list 的尾端，然後把右邊的 list 一個個從這邊接上。那 difference list 是怎麼作的呢？appending 兩個 difference list 就像這樣

```haskell
f `append` g = \xs -> f (g xs)
```

`f` 跟 `g` 這邊是兩個函數，他們都接受一個 list 並 prepend 另一串 list。舉例來說，如果 `f` 代表 `("dog"++)`（可以寫成 `\xs -> "dog" ++ xs`）而 `g` 是 `("meat"++)`，那 ``f `append` g`` 就會做成一個新的函數，等價於：

```haskell
\xs -> "dog" ++ ("meat" ++ xs)
```

append 兩個 difference list 其實就是用一個函數，這函數先餵一個 list 給第一個 difference list，然後再把結果餵給第二個 difference list。

我們可以用一個 `newtype` 來包起來

```haskell
newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
```

我們包起來的型態是 `[a] -> [a]`，因為 difference list 不過就是一個轉換一個 list 到另一個 list 的函數。要把普通 list 轉換成 difference list 也很容易。

```haskell
toDiffList :: [a] -> DiffList a  
toDiffList xs = DiffList (xs++)  

fromDiffList :: DiffList a -> [a]  
fromDiffList (DiffList f) = f []
```

要把一個普通 list 轉成 difference list 不過就是照之前定義的，作一個 prepend 另一個 list 的函數。由於 difference list 只是一個 prepend 另一串 list 的一個函數，假如我們要轉回來的話，只要餵給他空的 list 就行了。

這邊我們給一個 difference list 的 `Monoid` 定義

```haskell
instance Monoid (DiffList a) where  
    mempty = DiffList (\xs -> [] ++ xs)  
    (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))
```

我們可以看到 `mempty` 不過就是 `id`，而 `mappend` 其實是 function composition。

```haskell
ghci> fromDiffList (toDiffList [1,2,3,4] `mappend` toDiffList [1,2,3])  
[1,2,3,4,1,2,3]
```

現在我們可以用 difference list 來加速我們的 `gcdReverse`

```haskell
import Control.Monad.Writer  

gcd' :: Int -> Int -> Writer (DiffList String) Int  
gcd' a b  
  | b == 0 = do  
      tell (toDiffList ["Finished with " ++ show a])  
      return a  
  | otherwise = do  
      result <- gcd' b (a `mod` b)  
      tell (toDiffList [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)])  
      return result
```

我們只要把 monoid 的型態從 `[String]` 改成 `DiffList String`，並在使用 `tell` 的時候把普通的 list 用 `toDiffList` 轉成 difference list 就可以了。

```haskell
ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ gcdReverse 110 34  
Finished with 2  
8 mod 2 = 0  
34 mod 8 = 2  
110 mod 34 = 8
```

我們用 `runWriter` 來取出 `gcdReverse 110 34` 的結果，然後用 `snd` 取出 log，並用 `fromDiffList` 轉回普通的 list 印出來。

### Comparing Performance

要體會 Difference List 能如何增進效率，考慮一個從某數數到零的 case。我們紀錄的時候就像 `gcdReverse` 一樣是反過來記的，所以在 log 中實際上是從零數到某個數。

```haskell
finalCountDown :: Int -> Writer (DiffList String) ()  
finalCountDown 0 = do  
    tell (toDiffList ["0"])  
finalCountDown x = do  
    finalCountDown (x-1)  
    tell (toDiffList [show x])
```

如果我們餵 `0`，他就只 log 0。如果餵其他正整數，他會先倒數到 `0` 然後 append 那些數到 log 中，所以如果我們呼叫 `finalCountDown` 並餵給他 `100`，那 log 的最後一筆就會是 `"100"`。

如果你把這個函數 load 進 GHCi 中並餵給他一個比較大的整數 `500000`，你會看到他無停滯地從 `0` 開始數起：

```haskell
ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ finalCountDown 500000  
0  
1  
2
```

但如果我們用普通的 list 而不用 difference list

```haskell
finalCountDown :: Int -> Writer [String] ()  
finalCountDown 0 = do  
    tell ["0"]  
finalCountDown x = do  
    finalCountDown (x-1)  
    tell [show x]
```

並下同樣的指令

```haskell
ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000
```

我們會看到整個運算卡卡的。

當然這不是一個嚴謹的測試方法，但足以表顯出 difference list 是比較有效率的寫法。

## Reader Monad

![](/files/-LjUms-I-lDphiZFIBd-)

在講 Applicative 的章節中，我們說過了 `(->) r` 的型態只是 `Functor` 的一個 instance。要將一個函數 `f` map over 一個函數 `g`，基本上等價於一個函數，他可以接受原本 `g` 接受的參數，先套用 `g` 然後再把其結果丟給 `f`。

```haskell
ghci> let f = (*5)  
ghci> let g = (+3)
ghci> (fmap f g) 8
```

我們已經見識過函數當作 applicative functors 的例子。這樣能讓我們對函數的結果直接進行操作。

```haskell
ghci> let f = (+) <$> (*2) <*> (+10)
ghci> f 3
19
```

`(+) <$> (*2) <*> (+10)` 代表一個函數，他接受一個數值，分別把這數值交給 `(*2)` 跟 `(+10)`。然後把結果加起來。例如說，如果我們餵 `3` 給這個函數，他會分別對 `3` 做 `(*2)` 跟 `(+10)` 的動作。而得到 `6` 跟 `13`。然後呼叫 `(+)`，而得到 `19`。

其實 `(->) r` 不只是一個 functor 跟一個 applicative functor，他也是一個 monad。就如其他 monadic value 一般，一個函數也可以被想做是包含一個 context 的。這個 context 是說我們期待某個值，他還沒出現，但我們知道我們會把他當作函數的參數，呼叫函數來得到結果。

我們已經見識到函數是怎樣可以看作 functor 或是 applicative functors 了。再來讓我們看看當作 `Monad` 的一個 instance 時會是什麼樣子。你可以在 `Control.Monad.Instances` 裡面找到，他看起來像這樣：

```haskell
instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w
```

我們之前已經看過函數的 `pure` 實作了，而 `return` 差不多就是 `pure`。他接受一個值並把他放進一個 minimal context 裡面。而要讓一個函數能夠是某個定值的唯一方法就是讓他完全忽略他的參數。

而 `>>=` 的實作看起來有點難以理解，我們可以仔細來看看。當我們使用 `>>=` 的時候，餵進去的是一個 monadic value，處理他的是一個函數，而吐出來的也是一個 monadic value。在這個情況下，當我們將一個函數餵進一個函數，吐出來的也是一個函數。這就是為什麼我們在最外層使用了一個 lambda。在我們目前看過的實作中，`>>=` 幾乎都是用 lambda 將內部跟外部隔開來，然後在內部來使用 `f`。這邊也是一樣的道理。要從一個函數得到一個結果，我們必須餵給他一些東西，這也是為什麼我們先用 `(h w)` 取得結果，然後將他丟給 `f`。而 `f` 回傳一個 monadic value，在這邊這個 monadic value 也就是一個函數。我們再把 `w` 餵給他。

如果你還不太懂 `>>=` 怎麼寫出來的，不要擔心，因為接下來的範例會讓你曉得這真的是一個簡單的 Monad。我們造一個 `do` expression 來使用這個 Monad。

```
import Control.Monad.Instances  

addStuff :: Int -> Int  
addStuff = do  
  a <- (*2)  
  b <- (+10)  
  return (a+b)
```

這跟我們之前寫的 applicative expression 差不多，只差在他是運作在 monad 上。一個 `do` expression 的結果永遠會是一個 monadic vlaue，這個也不例外。而這個 monadic value 其實是一個函數。只是在這邊他接受一個數字，然後套用 `(*2)`，把結果綁定到 `a` 上面。而 `(+10)` 也同用被套用到同樣的參數。結果被綁定到 `b` 上。`return` 就如其他 monad 一樣，只是製作一個簡單的 monadic value 而不會作多餘的事情。這讓整個函數的結果是 `a+b`。如果我們試著跑跑看，會得到之前的結果。

```haskell
ghci> addStuff 3  
19
```

其中 `3` 會被餵給 `(*2)` 跟 `(+10)`。而且他也會被餵給 `return (a+b)`，只是他會忽略掉 `3` 而永遠回傳 `a+b` 正因為如此，function monad 也被稱作 reader monad。所有函數都從一個固定的地方讀取。要寫得更清楚一些，可以把 `addStuff` 改寫如下：

```haskell
addStuff :: Int -> Int  
addStuff x = let  
    a = (*2) x  
    b = (+10) x  
    in a+b
```

我們見識了把函數視作具有 context 的值很自然的可以表達成 reader monad。只要我們當作我們知道函數會回傳什麼值就好。他作的就是把所有的函數都黏在一起做成一個大的函數，然後把這個函數的參數都餵給全部組成的函數，這有點取出他們未來的值的意味。實作做完了然後 `>>=` 就會保證一切都能正常運作。

## State Monad

![](/files/-LjUms-QaYVNdqqym7r8)

Haskell 是一個純粹的語言，正因為如此，我們的程式是有一堆沒辦法改變全域狀態或變數的函數所組成，他們只會作些處理並回傳結果。這樣的性質讓我們很容易思考我們的程式在幹嘛，因為我們不需要擔心變數在某一個時間點的值是什麼。然而，有一些領域的問題根本上就是依賴於隨著時間而改變的狀態。雖然我們也可以用 Haskell 寫出這樣的程式，但有時候寫起來蠻痛苦的。這也是為什麼 Haskell 要加進 State Monad 這個特性。這讓我們在 Haskell 中可以容易地處理狀態性的問題，並讓其他部份的程式還是保持純粹性。

當我們處理亂數的時候，我們的函數接受一個 random generator 並回傳一個新的亂數跟一個新的 random generator。如果我們需要很多個亂數，我們可以用前一個函數回傳的 random generator 繼續做下去。當我們要寫一個接受 `StdGen` 的函數並產生丟三個硬幣結果的函數，我們會這樣寫：

```haskell
threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let (firstCoin, newGen) = random gen  
        (secondCoin, newGen') = random newGen  
        (thirdCoin, newGen''') = random newGen'  
    in  (firstCoin, secondCoin, thirdCoin)
```

他接受一個 `gen` 然後用 `random gen` 產生一個 `Bool` 型態的值以及新的 generator。要模擬丟第二個硬幣的話，便使用新的 generator。在其他語言中，多半除了亂數之外不需要多回傳一個 generator。那是因為我們可以對現有的進行修改。但 Haskell 是純粹的語言，我們沒辦法那麼做，所以我們必須要接受一個狀態，產生結果然後回傳一個新的狀態，然後用新的狀態來繼續做下去。

一般來講你應該不會喜歡這麼寫，在程式中有赤裸裸的狀態，但我們又不想放棄 Haskell 的純粹性質。這就是 State Monad 的好處了，他可以幫我們處理這些瑣碎的事情，又讓我們保持 Haskell 的純粹性。

為了深入理解狀態性的計算，我們先來看看應該給他們什麼樣的型態。我們會說一個狀態性的計算是一個函數，他接受一個狀態，回傳一個值跟一個新的狀態。寫起來會像這樣：

```haskell
s -> (a,s)
```

`s` 是狀態的型態，而 `a` 是計算結果的型態。

```
在其他的語言中，賦值大多是被當作會改變狀態的操作。舉例來說，當我們在命令式語言寫 ``x = 5``，這通常代表的是把 ``5`` 指定給 ``x`` 這變數。而且這邊 ``5`` 是一個 expression。

如果你用函數語言的角度去思考，你可以把他想做是一個函數，接受一個狀態，並回傳結果跟新的狀態。那新的狀態代表所有已指定的值與新加入的變數。
```

這種改變狀態的計算，除了想做是一個接受狀態並回傳結果跟新狀態的函數外，也可以想做是具有 context 的值。 實際的值是結果。然而要得到結果，我們必須要給一個初始的狀態，才能得到結果跟最後的狀態。

### Stack and Stones

考慮現在我們要對一個堆疊的操作建立模型。你可以把東西推上堆疊頂端，或是把東西從頂端拿下來。如果你要的元素是在堆疊的底層的話，你必須要把他上面的東西都拿下來才能拿到他。

我們用一個 list 來代表我們的堆疊。而我們把 list 的頭當作堆疊的頂端。為了正確的建立模型，我們要寫兩個函數：`pop` 跟 `push`。`pop` 會接受一個堆疊，取下一個元素並回傳一個新的堆疊，這個新的堆疊不包含取下的元素。`push` 會接受一個元素，把他堆到堆疊中，並回傳一個新的堆疊，其包含這個新的元素。

```haskell
type Stack = [Int]  

pop :: Stack -> (Int,Stack)  
pop (x:xs) = (x,xs)  

push :: Int -> Stack -> ((),Stack)  
push a xs = ((),a:xs)
```

我們用 `()` 來當作 pushing 的結果，畢竟推上堆疊並不需要什麼回傳值，他的重點是在改變堆疊。注意到 `push` 跟 `pop` 都是改變狀態的計算，可以從他們的型態看出來。

我們來寫一段程式來模擬一個堆疊的操作。我們接受一個堆疊，把 `3` 推上去，然後取出兩個元素。

```haskell
stackManip :: Stack -> (Int, Stack)  
stackManip stack = let  
    ((),newStack1) = push 3 stack  
    (a ,newStack2) = pop newStack1  
    in pop newStack2
```

我們拿一個 `stack` 來作 `push 3 stack` 的動作，其結果是一個 tuple。tuple 的第一個部份是 `()`，而第二個部份是新的堆疊，我們把他命名成 `newStack1`。然後我們從 `newStack1` 上 pop 出一個數字。其結果是我們之前 push 上去的一個數字 `a`，然後把這個更新的堆疊叫做 `newStack2`。然後我們從 `newStack2` 上再 pop 出一個數字 `b`，並得到 `newStack3`。我們回傳一個 tuple 跟最終的堆疊。

```haskell
ghci> stackManip [5,8,2,1]  
(5,[8,2,1])
```

結果就是 `5` 跟新的堆疊 `[8,2,1]`。注意到 `stackManip` 是一個會改變狀態的操作。我們把一堆會改變狀態的操作綁在一起操作，有沒有覺得很耳熟的感覺。

`stackManip` 的程式有點冗長，因為我們要寫得太詳細，必須把狀態給每個操作，然後把新的狀態再餵給下一個。如果我們可以不要這樣作的話，那程式應該會長得像這樣：

```haskell
stackManip = do  
    push 3  
    a <- pop  
    pop
```

這就是 State Monad 在做的事。有了他，我們便可以免除於要把狀態操作寫得太明白的窘境。

### The State Monad

`Control.Monad.State` 這個模組提供了一個 `newtype` 包起來的型態。

```haskell
newtype State s a = State { runState :: s -> (a,s) }
```

一個 `State s a` 代表的是一個改變狀態的操作，他操縱的狀態為型態 `s`，而產生的結果是 `a`。

我們已經見識過什麼是改變狀態的操作，以及他們是可以被看成具有 context 的值。接著來看看他們 `Monad` 的 instance：

```haskell
instance Monad (State s) where  
    return x = State $ \s -> (x,s)  
    (State h) >>= f = State $ \s -> let (a, newState) = h s  
                                        (State g) = f a  
                                    in  g newState
```

我們先來看看 `return` 那一行。我們 `return` 要作的事是接受一個值，並做出一個改變狀態的操作，讓他永遠回傳那個值。所以我們才做了一個 lambda 函數，`\s -> (x,s)`。我們把 `x` 當成是結果，並且狀態仍然是 `s`。這就是 `return` 要完成的 minimal context。

![](/files/-LjUms-SWiajCAevvOZc)

那 `>>=` 的實作呢？很明顯的把改變狀態的操作餵進 `>>=` 也必須要丟出另一個改變狀態的操作。所以我們用 `State` 這個 `newtype` wrapper 來把一個 lambda 函數包住。這個 lambda 會是新的一個改變狀態的操作。但裡面的內容是什麼？首先我們應該要從接受的操作取出結果。由於 lambda 是在一個大的操作中，所以我們可以餵給 `h` 我們現在的狀態，也就是 `s`。那會產生 `(a, newState)`。到目前為止每次我們在實作 `>>=` 的時候，我們都會先從 monadic value 中取出結果，然後餵給 `f` 來得到新的 monadic value。在寫 `Writer` 的時候，我們除了這樣作還要確保 context 是用 `mappend` 把舊的 monoid value 跟新的接起來。在這邊我們則是用 `f a` 得到一個新的操作 `g`。現在我們有了新的操作跟新的狀態（叫做 `newState`），我們就把 `newState` 餵給 `g`。結果便是一個 tuple，裡面包含了最後的結果跟最終的狀態。

有了 `>>=`，我們便可以把兩個操作黏在一起，只是第二個被放在一個函數中，專門接受第一個的結果。由於 `pop` 跟 `push` 已經是改變狀態的操作了，我們可以把他們包在 `State` 中

```haskell
import Control.Monad.State  

pop :: State Stack Int  
pop = State $ \(x:xs) -> (x,xs)  

push :: Int -> State Stack ()  
push a = State $ \xs -> ((),a:xs)
```

`pop` 已經滿足我們的條件，而 `push` 要先接受一個 `Int` 才會回傳我們要的操作。所以我們可以改寫先前的範例如下：

```haskell
import Control.Monad.State  

stackManip :: State Stack Int  
stackManip = do  
  push 3  
  a <- pop  
  pop
```

看到我們是怎麼把一個 `push` 跟兩個 `pop` 黏成一個操作嗎？當我們將他們從一個 `newtype` 取出，其實就是需要一個能餵進初始狀態的函數：

```haskell
ghci> runState stackManip [5,8,2,1]  
(5,[8,2,1])
```

我們不須綁定第二個 `pop`，因為我們根本不會用到 `a`，所以可以寫成下面的樣子：

```haskell
stackManip :: State Stack Int  
stackManip = do  
    push 3  
    pop  
    pop
```

再來嘗試另外一種方式，先從堆疊上取下一個數字，看看他是不是 `5`，如果是的話就把他放回堆疊上，如果不是的話就堆上 `3` 跟 `8`。

```haskell
stackStuff :: State Stack ()  
stackStuff = do  
    a <- pop  
    if a == 5  
        then push 5  
        else do  
            push 3  
            push 8
```

很直覺吧！我們來看看初始的堆疊的樣子。

```haskell
ghci> runState stackStuff [9,0,2,1,0]  
((),[8,3,0,2,1,0])
```

還記得我們說過 `do` 的結果會是一個 monadic value，而在 `State` monad 的 case，`do` 也就是一個改變狀態的函數。而由於 `stackManip` 跟 `stackStuff` 都是改變狀態的計算，因此我們可以把他們黏在一起：

```haskell
moreStack :: State Stack ()  
moreStack = do  
    a <- stackManip  
    if a == 100  
        then stackStuff  
        else return ()
```

如果 `stackManip` 的結果是 `100`，我們就會跑 `stackStuff`，如果不是的話就什麼都不做。`return ()` 不過就是什麼是都不做，全部保持原樣。

`Contorl.Monad.State` 提供了一個 `MonadState` 的 typeclass，他有兩個有用的函數，分別是 `get` 跟 `put`。對於 `State` 來說，`get` 的實作就像這樣：

```haskell
get = State $ \s -> (s,s)
```

他只是取出現在的狀態除此之外什麼也不做。而 `put` 函數會接受一個狀態並取代掉現有的狀態。

```haskell
put newState = State $ \s -> ((),newState)
```

有了這兩個狀態，我們便可以看到現在堆疊中有什麼，或是把整個堆疊中的元素換掉。

```haskell
stackyStack :: State Stack ()  
stackyStack = do  
    stackNow <- get  
    if stackNow == [1,2,3]  
        then put [8,3,1]  
        else put [9,2,1]
```

我們可以看看對於 `State` 而言，`>>=` 的型態會是什麼：

```haskell
(>>=) :: State s a -> (a -> State s b) -> State s b
```

我們可以看到狀態的型態都是 `s`，而結果從型態 `a` 變成型態 `b`。這代表我們可以把好幾個改變狀態的計算黏在一起，這些計算的結果可以都不一樣，但狀態的型態會是一樣的。舉例來說，對於 `Maybe` 而言，`>>=` 的型態會是：

```haskell
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
```

`Maybe` 不變是有道理的，但如果用 `>>=` 來把兩種不同的 monad 接起來是沒道理的。但對於 state monad 而言，monad 其實是 `State s`，所以如果 `s` 不一樣，我們就要用 `>>=` 來把兩個 monad 接起來。

### 隨機性與 state monad

在章節的一開始，我們知道了在 Haskell 中要產生亂數的不方便。我們要拿一個產生器，並回傳一個亂數跟一個新的產生器。接下來我們還一定要用新的產生器不可。但 State Monad 讓我們可以方便一些。

`System.Random` 中的 `random` 函數有下列的型態：

```haskell
random :: (RandomGen g, Random a) => g -> (a, g)
```

代表他接受一個亂數產生器，並產生一個亂數跟一個新的產生器。很明顯他是一個會改變狀態的計算，所以我們可以用 `newtype` 把他包在一個 `State` 中，然後把他當作 monadic value 來操作。

```haskell
import System.Random  
import Control.Monad.State  

randomSt :: (RandomGen g, Random a) => State g a  
randomSt = State random
```

這樣我們要丟三個硬幣的結果可以改寫成這樣：

```haskell
import System.Random  
import Control.Monad.State  

threeCoins :: State StdGen (Bool,Bool,Bool)  
threeCoins = do  
  a <- randomSt  
  b <- randomSt  
  c <- randomSt  
  return (a,b,c)
```

`threeCoins` 是一個改變狀態的計算，他接受一個初始的亂數產生器，他會把他餵給 `randomSt`，他會產生一個數字跟一個新的產生器，然後會一直傳遞下去。我們用 `return (a,b,c)` 來呈現 `(a,b,c)`，這樣並不會改變最近一個產生器的狀態。

```haskell
ghci> runState threeCoins (mkStdGen 33)  
((True,False,True),680029187 2103410263)
```

要完成像這樣要改變狀態的任務便因此變得輕鬆了很多。

## Error Monad

我們知道 `Maybe` 是拿來賦予一個值具有可能失敗的 context。一個值可能會是 `Just something` 或是一個 `Nothing`。儘管這很有用，但當我們拿到了一個 `Nothing`，我們只知道他失敗了，但我們沒辦法塞進一些有用的資訊，告訴我們究竟是在什麼樣的情況下失敗了。

而 `Either e a` 則能讓我們可以加入一個可能會發生錯誤的 context，還可以增加些有用的訊息，這樣能讓我們知道究竟是什麼東西出錯了。一個 `Either e a` 的值可以是代表正確的 `Right`，或是代表錯誤的 `Left`，例如說：

```haskell
ghci> :t Right 4  
Right 4 :: (Num t) => Either a t  
ghci> :t Left "out of cheese error"  
Left "out of cheese error" :: Either [Char] b
```

這就像是加強版的 `Maybe`，他看起來實在很像一個 monad，畢竟他也可以當作是一個可能會發生錯誤的 context，只是多了些訊息罷了。

在 `Control.Monad.Error` 裡面有他的 `Monad` instance。

```haskell
instance (Error e) => Monad (Either e) where  
    return x = Right x   
    Right x >>= f = f x  
    Left err >>= f = Left err  
    fail msg = Left (strMsg msg)
```

`return` 就是建立起一個最小的 context，由於我們用 `Right` 代表正確的結果，所以他把值包在一個 `Right` constructor 裡面。就像實作 `Maybe` 時的 `return` 一樣。

`>>=` 會檢查兩種可能的情況：也就是 `Left` 跟 `Right`。如果進來的是 `Right`，那我們就呼叫 `f`，就像我們在寫 `Just` 的時候一樣，只是呼叫對應的函數。而在錯誤的情況下，`Left` 會被傳出來，而且裡面保有描述失敗的值。

`Either e` 的 `Monad` instance 有一項額外的要求，就是包在 `Left` 中的型態，也就是 `e`，必須是 `Error` typeclass 的 instance。`Error` 這個 typeclass 描述一個可以被當作錯誤訊息的型態。他定義了 `strMsg` 這個函數，他接受一個用字串表達的錯誤。一個明顯的範例就是 `String` 型態，當他是 `String` 的時候，`strMsg` 只不過回傳他接受到的字串。

```haskell
ghci> :t strMsg  
strMsg :: (Error a) => String -> a  
ghci> strMsg "boom!" :: String  
"boom!"
```

但因為我們通常在用 `Either` 來描述錯誤的時候，是用 `String` 來裝錯誤訊息，所以我們也不用擔心這一點。當在 `do` 裡面做 pattern match 失敗的時候，`Left` 的值會拿來代表失敗。

總之來看看一個範例吧：

```haskell
ghci> Left "boom" >>= \x -> return (x+1)  
Left "boom"  
ghci> Right 100 >>= \x -> Left "no way!"  
Left "no way!"
```

當我們用 `>>=` 來把一個 `Left` 餵進一個函數，函數的運算會被忽略而直接回傳丟進去的 `Left` 值。當我們餵 `Right` 值給函數，函數就會被計算而得到結果，但函數還是產生了一個 `Left` 值。

當我們試著餵一個 `Right` 值給函數，而且函數也成功地計算，我們卻碰到了一個奇怪的 type error。

```haskell
ghci> Right 3 >>= \x -> return (x + 100)  

<interactive>:1:0:  
  Ambiguous type variable `a' in the constraints:  
    `Error a' arising from a use of `it' at <interactive>:1:0-33  
    `Show a' arising from a use of `print' at <interactive>:1:0-33  
  Probable fix: add a type signature that fixes these type variable(s)
```

Haskell 警告說他不知道要為 `e` 選擇什麼樣的型態，儘管我們是要印出 `Right` 的值。這是因為 `Error e` 被限制成 `Monad`。把 `Either` 當作 Monad 使用就會碰到這樣的錯誤，你只要明確寫出 type signature 就行了：

```haskell
ghci> Right 3 >>= \x -> return (x + 100) :: Either String Int  
Right 103
```

這樣就沒問題了。

撇除這個小毛病，把 `Either` 當 Monad 使用就像使用 `Maybe` 一樣。在前一章中，我們展示了 `Maybe` 的使用方式。你可以把前一章的範例用 `Either` 重寫當作練習。

## 一些實用的 Moandic functions

在這個章節，我們會看看一些操作 monadic value 的函數。這樣的函數通常我們稱呼他們為 monadic function。其中有些你是第一次見到，但有些不過是 `filter` 或 `foldl` 的變形。讓我們來看看吧！

### liftM

![](/files/-LjUms-jaw-YR_-M6fgi)

當我們開始學習 Monad 的時候，我們是先學習 functors，他代表可以被 map over 的事物。接著我們學了 functors 的加強版，也就是 applicative functors，他可以對 applicative values 做函數的套用，也可以把一個一般值放到一個預設的 context 中。最後，我們介紹在 applicative functors 上更進一步的 monad，他讓這些具有 context 的值可以被餵進一般函數中。

也就是說每一個 monad 都是個 applicative functor，而每一個 applicative functor 也都是一個 functor。`Applicative` typeclass 中有加入限制，讓每一個 `Applicative` 都是 `Functor`。但 `Monad` 卻沒有這樣的限制，讓每個 `Monad` 都是 `Applicative`。這是因為 `Monad` 這個 typeclass 是在 `Applicative` 引入前就存在的緣故。

但即使每個 monad 都是一個 functor，但我們不需要依賴 `Functor` 的定義。那是因為我們有 `liftM` 這個函數。他會接受一個函數跟一個 monadic value，然後把函數 map over 那些 monadic value。所以他其實就是 `fmap`，以下是他的型態：

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b
```

而這是 `fmap` 的型態：

```haskell
fmap :: (Functor f) => (a -> b) -> f a -> f b
```

如果 `Functor` 跟 `Monad` 的 instance 遵守 functor 跟 monad 的法則（到目前為止我們看過的 monad 都遵守），那這兩個函數其實是等價的。這就像 `pure` 跟 `return` 其實是同一件事，只是一個在 `Applicative` 中，而另外一個在 `Monad` 裡面，我們來試試看 `liftM` 吧：

```haskell
ghci> liftM (*3) (Just 8)  
Just 24  
ghci> fmap (*3) (Just 8)  
Just 24  
ghci> runWriter $ liftM not $ Writer (True, "chickpeas")  
(False,"chickpeas")  
ghci> runWriter $ fmap not $ Writer (True, "chickpeas")  
(False,"chickpeas")  
ghci> runState (liftM (+100) pop) [1,2,3,4]  
(101,[2,3,4])  
ghci> runState (fmap (+100) pop) [1,2,3,4]  
(101,[2,3,4])
```

我們已經知道 `fmap` 是如何運作在 `Maybe` 上。而 `liftM` 又跟 `fmap` 等價。對於 `Writer` 型態的值而言，函數只有對他的第一個 component 做處理。而對於改變狀態的計算，`fmap` 跟 `liftM` 也都是產生另一個改變狀態的計算。我們也看過了 `(+100)` 當作用在 `pop` 上會產生 `(1, [2,3,4])`。

來看看 `liftM` 是如何被實作的：

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b  
liftM f m = m >>= (\x -> return (f x))
```

或者用 `do` 來表示得清楚些

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b  
liftM f m = do  
    x <- m  
    return (f x)
```

我們餵一個 monadic value `m` 給函數，我們套用那個函數然後把結果放進一個預設的 context。由於遵守 monad laws，這保證這操作不會改變 context，只會呈現最後的結果。我們可以看到實作中 `liftM` 也沒有用到 `Functor` 的性質。這代表我們能只用 monad 提供給我們的就實作完 `fmap`。這特性讓我們可以得到 monad 比 functor 性質要強的結論。

`Applicative` 讓我們可以操作具有 context 的值就像操作一般的值一樣。 就像這樣：

```haskell
ghci> (+) <$> Just 3 <*> Just 5  
Just 8  
ghci> (+) <$> Just 3 <*> Nothing  
Nothing
```

使用 applicative 的特性讓事情變得很精簡。 `<$>` 不過就是 `fmap`，而 `<*>` 只是一個具有下列型態的函數：

```haskell
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
```

他有點像 `fmap`，只是函數本身有一個 context。我們必須把他從 context 中抽出，對 `f a` 做 map over 的東做，然後再放回 context 中。由於在 Haskel 中函數預設都是 curried，我們便能用 `<$>` 以及 `<*>` 來讓接受多個參數的函數也能接受 applicative 種類的值。

總之 `<*>` 跟 `fmap` 很類似，他也能只用 `Monad` 保證的性質實作出來。`ap` 這個函數基本上就是 `<*>`，只是他是限制在 `Monad` 上而不是 `Applicative` 上。這邊是他的定義：

```haskell
ap :: (Monad m) => m (a -> b) -> m a -> m b  
ap mf m = do  
    f <- mf  
    x <- m  
    return (f x)
```

`mf` 是一個 monadic value，他的結果是一個函數。由於函數跟值都是放在 context 中，假設我們從 context 取出的函數叫 `f`，從 context 取出的值叫 `x`，我們把 `x` 餵給 `f` 然後再把結果放回 context。像這樣：

```haskell
ghci> Just (+3) <*> Just 4  
Just 7  
ghci> Just (+3) `ap` Just 4  
Just 7  
ghci> [(+1),(+2),(+3)] <*> [10,11]  
[11,12,12,13,13,14]  
ghci> [(+1),(+2),(+3)] `ap` [10,11]  
[11,12,12,13,13,14]
```

由於我們能用 `Monad` 提供的函數實作出 `Applicative` 的函數，因此我們看到 Monad 有比 applicative 強的性質。事實上，當我們知道一個型態是 monad 的時候，大多數會先定義出 `Monad` 的 instance，然後才定義 `Applicative` 的 instance。而且只要把 `pure` 定義成 `return`，`<*>` 定義成 `ap` 就行了。同樣的，如果你已經有了 `Monad` 的 instance，你也可以簡單的定義出 `Functor`，只要把 `fmap` 定義成 `liftM` 就行了。

`liftA2` 是一個方便的函數，他可以把兩個 applicative 的值餵給一個函數。他的定義很簡單：

```haskell
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c  
liftA2 f x y = f <$> x <*> y
```

`liftM2` 也是做差不多的事情，只是多了 `Monad` 的限制。在函式庫中其實也有 `liftM3`，`liftM4` 跟 `liftM5`。

我們看到了 monad 相較於 applicative 跟 functor 有比較強的性質。儘管 moand 有 functor 跟 applicative functor 的性質，但他們不見得有 `Functor` 跟 `Applicative` 的 instance 定義。所以我們檢視了一些在 monad 中定義，且等價於 functor 或 applicative functor 所具有的函數。

### The join function

如果一個 monadic value 的結果是另一個 monadic value，也就是其中一個 monadic value 被包在另一個裡面，你能夠把他們變成一個普通的 monadic value 嗎？就好像把他們打平一樣。譬如說，我們有 `Just (Just 9)`，我們能夠把他變成 `Just 9` 嗎？事實上是可以的，這也是 monad 的一個性質。也就是我要看的 `join` 函數，他的型態是這樣：

```haskell
join :: (Monad m) => m (m a) -> m a
```

他接受一個包在另一個 monadic value 中的 monadic value，然後會回給我們一個普通的 monadic value。這邊有一些 `Maybe` 的範例：

```haskell
ghci> join (Just (Just 9))  
Just 9  
ghci> join (Just Nothing)  
Nothing  
ghci> join Nothing  
Nothing
```

第一行是一個計算成功的結果包在另一個計算成功的結果，他們應該要能結合成為一個比較大的計算成功的結果。第二行則是一個 `Nothing` 包在一個 `Just` 中。我們之前在處理 `Maybe` 型態的值時，會用 `<*>` 或 `>>=` 把他們結合起來。輸入必須都是 `Just` 時結果出來才會是 `Just`。如果中間有任何的失敗，結果就會是一個失敗的結果。而第三行就是這樣，我們嘗試把失敗的結果接合起來，結果也會是一個失敗。

要 `join` 一個 list 也是很簡單：

```haskell
ghci> join [[1,2,3],[4,5,6]]  
[1,2,3,4,5,6]
```

你可以看到，對於 list 而言 `join` 不過就是 `concat`。 而要 `join` 一個包在 `Writer` 中的 `Writer`， 我們必須用 `mappend`：

```haskell
ghci> runWriter $ join (Writer (Writer (1,"aaa"),"bbb"))  
(1,"bbbaaa")
```

`"bbb"` 先被加到 monoid 中，接著 `"aaa"` 被附加上去。你想要檢視 `Writer` 中的值的話，必須先把值寫進去才行。

要對 `Either` 做 `join` 跟對 `Maybe` 做 `join` 是很類似的：

```haskell
ghci> join (Right (Right 9)) :: Either String Int  
Right 9  
ghci> join (Right (Left "error")) :: Either String Int  
Left "error"  
ghci> join (Left "error") :: Either String Int  
Left "error"
```

如果我們對一個包了另外一個改變狀態的計算的進行改變狀態的計算，要作 `join` 的動作會讓外面的先被計算，然後才是計算裡面的：

```haskell
ghci> runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0]  
((),[10,1,2,0,0,0])
```

這邊的 lambda 函數接受一個狀態，並把 `2` 跟 `1` 放到堆疊中，並把 `push 10` 當作他的結果。當對整個東西做 `join` 的時候，他會先把 `2` 跟 `1` 放到堆疊上，然後進行 `push 10` 的計算，因而把 `10` 放到堆疊的頂端。

`join` 的實作像是這樣：

```haskell
join :: (Monad m) => m (m a) -> m a  
join mm = do  
    m <- mm  
    m
```

因為 `mm` 的結果會是一個 monadic value，我們單獨用 `m <- mm` 拿取他的結果。這也可以說明 `Maybe` 只有當外層跟內層的值都是 `Just` 的時候才會是 `Just`。如果把 `mm` 的值設成 `Just (Just 8)` 的話，他看起來會是這樣：

```haskell
joinedMaybes :: Maybe Int  
joinedMaybes = do  
    m <- Just (Just 8)  
    m
```

![](/files/-LjUms-tYuYicKWETNRF)

最有趣的是對於一個 monadic value 而言，用 `>>=` 把他餵進一個函數其實等價於對 monad 做 mapping over 的動作，然後用 `join` 來把值從 nested 的狀態變成扁平的狀態。也就是說 `m >>= f` 其實就是 `join (fmap f m)`。如果你仔細想想的話其實很明顯。`>>=` 的使用方式是，把一個 monadic value 餵進一個接受普通值的函數，但他卻會回傳 monadic value。如果我們 map over 一個 monadic value，我們會做成一個 monadic value 包了另外一個 monadic value。例如說，我們現在手上有 `Just 9` 跟 `\x -> Just (x+1)`。如果我們把這個函數 map over `Just 9`，我們會得到 `Just (Just 10)`

事實上 `m >>= f` 永遠等價於 `join (fmap f m)` 這性質非常有用。如果我們要定義自己的 `Monad` instance，要知道怎麼把 nested monadic value 變成扁平比起要定義 `>>=` 是比較容易的一件事。

### filterM

`filter` 函數是 Haskell 中不可或缺的要素。他接受一個斷言(predicate)跟一個 list 來過濾掉斷言為否的部份並回傳一個新的 list。他的型態是這樣：

```haskell
filter :: (a -> Bool) -> [a] -> [a]
```

predicate 能接 list 中的一個元素並回傳一個 `Bool` 型態的值。但如果 `Bool` 型態其實是一個 monadic value 呢？也就是他有一個 context。例如說除了 `True` 跟 `False` 之外還伴隨一個 monoid，像是 `["Accepted the number 5"]`，或 `["3 is too small"]`。照前面所學的聽起來是沒問題，而且產出的 list 也會跟隨 context，在這個例子中就是 log。所以如果 `Bool` 會回傳伴隨 context 的布林值，我們會認為最終的結果也會具有 context。要不然這些 context 都會在處理過程中遺失。

在 `Control.Monad` 中的 `filterM` 函數正是我們所需要的，他的型態如下：

```haskell
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
```

predicate 會回傳一個 monadic value，他的結果會是 `Bool` 型態，由於他是 monadic value，他的 context 有可能會是任何 context，譬如說可能的失敗，non-determinism，甚至其他的 context。一旦我們能保證 context 也會被保存在最後的結果中，結果也就是一個 monadic value。

我們來寫一個接受 list 然後過濾掉小於 4 的函數。先嘗試使用 `filter` 函數：

```haskell
ghci> filter (\x -> x < 4) [9,1,5,2,10,3]  
[1,2,3]
```

很簡單吧。接著我們在做個 predicate，除了表達 `True` 或 `False` 之外，還提供了一個 log。我們會用 `Writer` monad 來表達這件事：

```haskell
keepSmall :: Int -> Writer [String] Bool  
keepSmall x  
    | x < 4 = do  
        tell ["Keeping " ++ show x]  
        return True  
    | otherwise = do  
        tell [show x ++ " is too large, throwing it away"]  
        return False
```

這個函數會回傳 `Writer [String] Bool` 而不是一個單純的 `Bool`。他是一個 monadic predicate。如果掃到的數字小於 `4` 的話，我們就會回報要保存他，而且回傳 `return True`。

接著，我們把他跟一個 list 餵給 `filterM`。由於 predicate 會回傳 `Writer`，所以結果仍會是一個 `Writer` 值。

```haskell
ghci> fst $ runWriter $ filterM keepSmall [9,1,5,2,10,3]  
[1,2,3]
```

要檢查 `Writer` 的結果，我們想要印出 log 看看裡面有什麼東西：

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter $ filterM keepSmall [9,1,5,2,10,3]  
9 is too large, throwing it away  
Keeping 1  
5 is too large, throwing it away  
Keeping 2  
10 is too large, throwing it away  
Keeping 3
```

提供 monadic predicate 給 `filterM`，我們便能夠做 filter 的動作，同時還能保有 monadic context。

一個比較炫的技巧是用 `filterM` 來產生一個 list 的 powerset。一個 powerset 就是一個集合所有子集所形成的集合。如果說我們的 list 是 `[1,2,3]`，那他個 powerset 就會是：

```haskell
[1,2,3]  
[1,2]  
[1,3]  
[1]  
[2,3]  
[2]  
[3]  
[]
```

換句話說，要產生一個 powerset 就是要列出所有要丟掉跟保留的組合。`[2,3]` 只不過代表我們把 `1` 給丟掉而已。

我們要依賴 non-determinism 來寫我們這產生 powerset 的函數。我們接受一個 list `[1,2,3]` 然後查看第一個元素，這個例子中是 `1`，我們會問：我們要保留他呢？還是丟掉他呢？答案是我們都要做。所以我們會用一個 non-determinism 的 predicate 來過濾我的 list。也就是我們的 `powerset` 函數：

```haskell
powerset :: [a] -> [[a]]  
powerset xs = filterM (\x -> [True, False]) xs
```

等等，我們已經寫完了嗎？沒錯，就這麼簡單，我們可以同時丟掉跟保留每個元素。只要我們用 non-deterministic predicate，那結果也就是一個 non-deterministic value，也便是一個 list 的 list。試著跑跑看：

```haskell
ghci> powerset [1,2,3]  
[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]
```

這樣的寫法需要讓你好好想一下，但如果你能接受 list 其實就是 non-deterministic value 的話，那要想通會比較容易一些。

### foldM

`foldl` 的 monadic 的版本叫做 `foldM`。如果你還有印象的話，`foldl` 會接受一個 binary 函數，一個起始累加值跟一串 list，他會從左邊開始用 binary 函數每次帶進一個值來 fold。`foldM` 也是做同樣的事，只是他接受的這個 binary 函數會產生 monadic value。不意外的，他的結果也會是 monadic value。`foldl` 的型態是：

```haskell
foldl :: (a -> b -> a) -> a -> [b] -> a
```

而 `foldM` 的型態則是：

```haskell
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
```

binary 函數的回傳值是 monadic，所以結果也會是 monadic。我們來試著把 list 的值用 fold 全部加起來：

```haskell
ghci> foldl (\acc x -> acc + x) 0 [2,8,3,1]  
14
```

這邊起始的累加值是 `0`，首先 `2` 會被加進去，變成 `2`。然後 `8` 被加進去變成 `10`，直到我們沒有值可以再加，那便是最終的結果。

但如果我們想額外加一個條件，也就是當碰到一個數字大於 `9` 時候，整個運算就算失敗呢？一種合理的修改就是用一個 binary 函數，他會檢查現在這個數是否大於 `9`，如果是便引發失敗，如果不是就繼續。由於有失敗的可能性，我們便需要這個 binary 函數回傳一個 `Maybe`，而不是一個普通的值。我們來看看這個函數：

```haskell
binSmalls :: Int -> Int -> Maybe Int  
binSmalls acc x  
    | x > 9     = Nothing  
    | otherwise = Just (acc + x)
```

由於這邊的 binary 函數是 monadic function，我們不能用普通的 `foldl`，我們必須用 `foldM`：

```haskell
ghci> foldM binSmalls 0 [2,8,3,1]  
Just 14  
ghci> foldM binSmalls 0 [2,11,3,1]  
Nothing
```

由於這串 list 中有一個數值大於 `9`，所以整個結果會是 `Nothing`。另外你也可以嘗試 fold 一個回傳 `Writer` 的 binary 函數，他會在 fold 的過程中紀錄你想紀錄的資訊。

### Making a safe RPN calculator

![](/files/-LjUms-ziISQJ2u8MPUQ)

之前的章節我們實作了一個 RPN 計算機，但我們沒有做錯誤的處理。他只有在輸入是合法的時候才會運算正確。假如有東西出錯了，整個程式便會當掉。我們在這章看到了要怎樣把程式碼轉換成 monadic 的版本，我們先嘗適用 `Maybe` monad 來幫我們的 RPN 計算機加上些錯誤處理。

我們的 RPN 計算機接受一個像 `"1 3 + 2 *"` 這樣的字串，把他斷成 word，變成 `["1","3","+","2","*"]` 這樣。然後用一個 binary 函數，跟一個空的堆疊，從左邊開始或是將數值推進堆疊中，或是操作堆疊最上層的兩個元素。

以下便是程式的核心部份：

```haskell
import Data.List  

solveRPN :: String -> Double  
solveRPN = head . foldl foldingFunction [] . words
```

我們把輸入變成一個字串的 list，從左邊開始 fold，當堆疊中只剩下一個元素的時候，他便是我們要的答案。以下是我們的 folding 函數：

```haskell
foldingFunction :: [Double] -> String -> [Double]  
foldingFunction (x:y:ys) "*" = (x * y):ys  
foldingFunction (x:y:ys) "+" = (x + y):ys  
foldingFunction (x:y:ys) "-" = (y - x):ys  
foldingFunction xs numberString = read numberString:xs
```

這邊我們的累加元素是一個堆疊，我們用一個 `Double` 的 list 來表示他。當我們在做 folding 的過程，如果當前的元素是一個 operator，他會從堆疊上拿下兩個元素，用 operator 施行運算然後把結果放回堆疊。如果當前的元素是一個表示成字串的數字，他會把字串轉換成數字，並回傳一個新的堆疊包含了轉換後的數字。

我們首先把我們的 folding 函數加上處理錯誤的 case，所以他的型態會變成這樣：

```haskell
foldingFunction :: [Double] -> String -> Maybe [Double]
```

他不是回傳一個 `Just` 的堆疊就是回傳 `Nothing`。

`reads` 函數就像 `read` 一樣，差別在於他回傳一個 list。在成功讀取的情況下 list 中只包含讀取的那個元素。如果他失敗了，他會回傳一個空的 list。除了回傳讀取的元素，他也回傳剩下讀取失敗的元素。他必須要看完整串輸入，我們想把他弄成一個 `readMaybe` 的函數，好方便我們進行。

```haskell
readMaybe :: (Read a) => String -> Maybe a  
readMaybe st = case reads st of [(x,"")] -> Just x  
                                _ -> Nothing
```

測試結果如下：

```haskell
ghci> readMaybe "1" :: Maybe Int  
Just 1  
ghci> readMaybe "GO TO HELL" :: Maybe Int  
Nothing
```

看起來運作正常。我們再把他變成一個可以處理失敗情況的 monadic 函數

```haskell
foldingFunction :: [Double] -> String -> Maybe [Double]  
foldingFunction (x:y:ys) "*" = return ((x * y):ys)  
foldingFunction (x:y:ys) "+" = return ((x + y):ys)  
foldingFunction (x:y:ys) "-" = return ((y - x):ys)  
foldingFunction xs numberString = liftM (:xs) (readMaybe numberString)
```

前三種 case 跟前面的很像，只差在堆疊現在是包在 `Just` 裡面（我們常常是用 `return` 來做到這件事，但其實我們也可以用 `Just`）。在最後一種情況，我們用 `readMaybe numberString` 然後我們用 `(:xs)` map over 他。所以如果堆疊 `xs` 是 `[1.0,2.0]` 且 `readMaybe numberString` 產生 `Just 3.0`，那結果便是 `Just [3.0,1.0,2.0]`。如果 `readyMaybe numberString` 產生 `Nothing` 那結果便是 `Nothing`。我們來試著跑跑看 folding 函數

```haskell
ghci> foldingFunction [3,2] "*"  
Just [6.0]  
ghci> foldingFunction [3,2] "-"  
Just [-1.0]  
ghci> foldingFunction [] "*"  
Nothing  
ghci> foldingFunction [] "1"  
Just [1.0]  
ghci> foldingFunction [] "1 wawawawa"  
Nothing
```

看起來正常運作。我們可以用他來寫一個新的 `solveRPN`。

```haskell
import Data.List  

solveRPN :: String -> Maybe Double  
solveRPN st = do  
  [result] <- foldM foldingFunction [] (words st)  
  return result
```

我們仍是接受一個字串把他斷成一串 word。然後我們用一個空的堆疊來作 folding 的動作，只差在我們用的是 `foldM` 而不是 `foldl`。`foldM` 的結果會是 `Maybe`，`Maybe` 裡面包含了一個只有一個元素的 list。我們用 `do` expression 來取出值，把他綁定到 `result` 上。當 `foldM` 回傳 `Nothing` 的時候，整個結果就變成 `Nothing`。也特別注意我們有在 `do` 裡面做 pattern match 的動作，所以如果 list 中不是只有一個元素的話，最後結果便會是 `Nothing`。最後一行我們用 `return result` 來展示 RPN 計算的結果，把他包在一個 `Maybe` 裡面。

```haskell
ghci> solveRPN "1 2 * 4 +"  
Just 6.0  
ghci> solveRPN "1 2 * 4 + 5 *"  
Just 30.0  
ghci> solveRPN "1 2 * 4"  
Nothing  
ghci> solveRPN "1 8 wharglbllargh"  
Nothing
```

第一個例子會失敗是因為 list 中不是只有一個元素，所以 `do` 裡面的 pattern matching 失敗了。第二個例子會失敗是因為 `readMaybe` 回傳了 `Nothing`。

### Composing monadic functions

當我們介紹 monad law 的時候，我們說過 `<=<` 就像是函數合成一樣，只差在一個是作用在普通函數 `a -> b`。一個是作用在 monadic 函數 `a -> m b`。

```haskell
ghci> let f = (+1) . (*100)  
ghci> f 4  
401  
ghci> let g = (\x -> return (x+1)) <=< (\x -> return (x*100))  
ghci> Just 4 >>= g  
Just 401
```

在這個例子中我們合成了兩個普通的函數，並餵給給他 `4`。我們也合成了兩個 monadic 函數並用 `>>=` 餵給他 `Just 4`。

如果我們在 list 中有一大堆函數，我們可以把他們合成一個巨大的函數。用 `id` 當作累加的起點，`.` 當作 binary 函數，用 fold 來作這件事。

```haskell
ghci> let f = foldr (.) id [(+1),(*100),(+1)]  
ghci> f 1  
201
```

`f` 接受一個數字，然後會幫他加 `1`，乘以 `100`，再加 `1`。我們也可以將 monadic 函數用同樣的方式做合成，只是不用 `.` 而用 `<=<`，不用 `id` 而用 `return`。我們不需要 `foldM`，由於 `<=<` 只用 `foldr` 就足夠了。

當我們在之前的章節介紹 list monad 的時候， 我們用他來解決一個騎士是否能在三步內走到另一點的問題。 那個函數叫做 `moveKnight`， 他接受一個座標然後回傳所有可能的下一步。 然後產生出所有可能三步的移動。

```haskell
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
```

要檢查我們是否能只用三步從 `start` 走到 `end`，我們用下列函數

```haskell
canReachIn3 :: KnightPos -> KnightPos -> Bool  
canReachIn3 start end = end `elem` in3 start
```

如果使用 monadic 版本的合成的話，我們也可以做一個類似的 `in3`，但我們希望他不只有三步的版本，而希望有任意步的版本。如果你仔細觀察 `in3`，他只不過用 `>>=` 跟 `moveKnight` 把之前所有可能結果餵到下一步。把他一般化，就會像下面的樣子：

```haskell
import Data.List  

inMany :: Int -> KnightPos -> [KnightPos]  
inMany x start = return start >>= foldr (<=<) return (replicate x moveKnight)
```

首先我們用 `replicate` 來做出一個 list，裡面有 `x` 份的 `moveKnight`。然後我們把所有函數都合成起來，就會給我們從起點走 `x` 步內所有可能的的位置。然後我們只需要把起始位置餵給他就好了。

我們也可以一般化我們的 `canReachIn3`：

```haskell
canReachIn :: Int -> KnightPos -> KnightPos -> Bool  
canReachIn x start end = end `elem` inMany x start
```

## 定義自己的 Monad

![](/files/-LjUms03OcUO-_ZusIx9)

在這一章節，我們會帶你看看究竟一個型態是怎麼被辨認，確認是一個 monad 而且正確定義出 `Monad` 的 instance。我們通常不會為了定義 monad 而定義。比較常發生的是，我們想要針對一個問題建立模型，卻稍後發現我們定義的型態其實是一個 Monad，所以就定義一個 `Monad` 的 instance。

正如我們看到的，list 是被拿來當作 non-deterministic values。對於 `[3,5,9]`，我們可以看作是一個 non-deterministic value，我們不能知道究竟是哪一個。當我們把一個 list 用 `>>=` 餵給一個函數，他就是把一串可能的選擇都丟給函數，函數一個個去計算在那種情況下的結果，結果也便是一個 list。

如果我們把 `[3,5,9]` 看作是 `3`,`5`,`9` 各出現一次，但這邊沒有每一種數字出現的機率。如果我們把 non-deterministic 的值看作是 `[3,5,9]`，但 `3` 出現的機率是 50%，`5` 跟 `9` 出現的機率各是 25%呢？我們來試著用 Haskell 描述看看。

如果說 list 中的每一個元素都伴隨著他出現的機率。那下面的形式就蠻合理的：

```haskell
[(3,0.5),(5,0.25),(9,0.25)]
```

在數學上，機率通常不是用百分比表示，而是用介於 0 跟 1 的實數表示。0 代表不可能會發生，而 1 代表絕對會發生。但浮點數很有可能很快隨著運算失去精準度，所以 Haskell 有提供有理數。他的型態是擺在 `Data.Ratio` 中，叫做 `Rational`。要創造出一個 `Rational`，我們會把他寫成一個分數的形式。分子跟分母用 `%` 分隔。這邊有幾個例子：

```haskell
ghci> 1%4  
1 % 4  
ghci> 1%2 + 1%2  
1 % 1  
ghci> 1%3 + 5%4  
19 % 12
```

第一行代表四分之一，第二行代表兩個二分之一加起來變成一。而第三行我們把三分之一跟四分之五加起來變成十二分之十九。所以我們來用 `Rational` 取代浮點數來當作我們的機率值吧。

```haskell
ghci> [(3,1%2),(5,1%4),(9,1%4)]  
[(3,1 % 2),(5,1 % 4),(9,1 % 4)]
```

所以 `3` 有二分之一的機會出現，而 `5` 跟 `9` 有四分之一的機會出現。

可以看到我們幫 list 加上了一些額外的 context。再我們繼續深入之前，我們用一個 `newtype` 把他包起來，好讓我們幫他寫 instance。

```haskell
import Data.Ratio

newtype Prob a = Prob { getProb :: [(a,Rational)] } deriving Show
```

接著我們想問，這是一個 functor 嗎？list 是一個 functor，所以很有可能他也是一個 functor，畢竟我們只是在 list 上多加一些東西而已。在 list 的情況下，我們可以針對每個元素用函數做處理。這邊我們也是用函數針對每個元素做處理，只是我們是輸出機率值。所以我們就來寫個 functor 的 instance 吧。

```haskell
instance Functor Prob where  
    fmap f (Prob xs) = Prob $ map (\(x,p) -> (f x,p)) xs
```

我們可以用 pattern matching 的方式把 `newtype` 解開來，套用函數 `f` 之後再包回去。過程中不會動到機率值。

```haskell
ghci> fmap negate (Prob [(3,1%2),(5,1%4),(9,1%4)])  
Prob {getProb = [(-3,1 % 2),(-5,1 % 4),(-9,1 % 4)]}
```

要注意機率的和永遠是 `1`。如果我們沒有漏掉某種情形的話，沒有道理他們加起來的值不為 `1`。一個有 75% 機率是正面以及 50% 機率是反面的硬幣根本沒什麼道理。

接著要問一個重要的問題，他是一個 monad 嗎？我們知道 list 是一個 monad，所以他很有可能也是一個 monad。首先來想想 `return`。他在 list 是怎麼運作的？他接受一個普通的值並把他放到一個 list 中變成只有一個元素的 list。那在這邊又如何？由於他是一個最小的 context，他也應該是一個元素的 list。那機率值呢？`return x` 的值永遠都是 `x`，所以機率值不應該是 `0`，而應該是 `1`。

至於 `>>=` 呢？看起來有點複雜，所以我們換種方式來思考，我們知道 `m >>= f` 會等價於 `join (fmap f m)`，所以我們來想要怎麼把一串包含 probability list 的 list 弄平。舉個例子，考慮一個 list，`'a'` 跟 `'b'` 恰出現其中一個的機率為 25%，兩個出現的機率相等。而 `'c'` 跟 `'d'` 恰出現其中一個的機率為 75%，兩個出現的機率也是相等。這邊有一個圖將情形畫出來。

![](/files/-LjUms05YEHbnwdoutUl)

每個字母發生的機率有多高呢？如果我們用四個盒子來代表每個字母，那每個盒子的機率為何？每個盒子的機率是他們所裝有的機率值相乘的結果。`'a'` 的機率是八分之一，`'b'` 同樣也是八分之一。八分之一是因為我們把二分之一跟四分之一相乘得到的結果。而 `'c'` 發生的機率是八分之三，是因為二分之一乘上四分之三。`'d'` 同樣也是八分之三。如果把所有的機率加起來，就會得到一，符合機率的規則。

來看看怎麼用一個 list 表達我們要說明的東西：

```haskell
thisSituation :: Prob (Prob Char)  
thisSituation = Prob  
    [( Prob [('a',1%2),('b',1%2)] , 1%4 )  
    ,( Prob [('c',1%2),('d',1%2)] , 3%4 )  
    ]
```

注意到這邊的型態是 `Prob (Prob Char)`。所以我們要思考的是如何把一串包含機率 list 的 list 打平。如果能成功寫出這樣的邏輯，`>>=` 不過就是 `join (fmap f m)`，我們便得到了一個 monad。我們這邊寫了一個 `flatten` 來做這件事。

```haskell
flatten :: Prob (Prob a) -> Prob a  
flatten (Prob xs) = Prob $ concat $ map multAll xs  
    where multAll (Prob innerxs,p) = map (\(x,r) -> (x,p*r)) innerxs
```

`multAll` 接受一個 tuple，裡面包含一個 probability list 跟一個伴隨的機率值 `p`，所以我們要作的事是把 list 裡面的機率值都乘以 `p`，並回傳一個新的 tuple 包含新的 list 跟新的機率值。我們將 `multAll` map over 到我們的 probability list 上，我們就成功地打平了我們的 list。

現在我們就能定義我們的 `Monad` instance。

```haskell
instance Monad Prob where  
    return x = Prob [(x,1%1)]  
    m >>= f = flatten (fmap f m)  
    fail _ = Prob []
```

![](/files/-LjUms07-37Ubty98yUu)

由於我們已經把所有苦工的做完了，定義這個 instance 顯得格外輕鬆。我們也定義了 `fail`，我們定義他的方式跟定義 list 一樣。如果在 `do` 中發生了失敗的 pattern match，那就會呼叫 `fail`。

檢查我們定義的 instance 是否遵守 monad law 也是很重要的。monad law 的第一個定律是 `return x >>= f` 應該要等價於 `f x`。要寫出嚴格的證明會很麻煩，但我們可以觀察到下列事實：首先用 `return` 做一個最小的 context，然後用 `fmap` 將一個函數 map over 這個 context，再將他打平。這樣做出來的 probability list，每一個機率值都相當於將我們最初放到 minimal context 中的值乘上 `1%1`。同樣的邏輯，也可以看出 `m >>= return` 是等價於 `m`。第三個定律是 `f <=< (g <=< h)` 應該要等價於 `(f <=< g) <=< h`。我們可以從乘法有結合律的性質，以及 list monad 的特性上推出 probability monad 也符合這個定律。`1%2 * (1%3 * 1%5)` 等於 `(1%2 * 1%3) * 1%5`。

現在我們有了一個 monad，這樣有什麼好處呢？他可以幫助我們計算機率值。我們可以把機率事件看作是具有 context 的 value，而 probability monad 可以保證機率值能正確地被計算成最終的結果。

好比說我們現在有兩個普通的硬幣以及一個灌鉛的硬幣。灌鉛的硬幣十次中有九次會出現正面，只有一次會出現反面。如果我們一次丟擲這三個硬幣，有多大的機會他們都會出現正面呢？讓我們先來表達丟擲硬幣這件事，分別丟的是灌鉛的跟普通的硬幣。

```haskell
data Coin = Heads | Tails deriving (Show, Eq)  

coin :: Prob Coin  
coin = Prob [(Heads,1%2),(Tails,1%2)]  

loadedCoin :: Prob Coin  
loadedCoin = Prob [(Heads,1%10),(Tails,9%10)]
```

最後，來看看擲硬幣的函數：

```haskell
import Data.List (all)  

flipThree :: Prob Bool  
flipThree = do  
  a <- coin  
  b <- coin  
  c <- loadedCoin  
  return (all (==Tails) [a,b,c])
```

試著跑一下的話，我們會看到儘管我們用了不公平的硬幣，三個反面的機率還是不高。

```haskell
ghci> getProb flipThree  
[(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),  
 (False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]
```

同時出現正面的機率是四十分之九，差不多是 25%的機會。我們的 monad 並沒有辦法 join 所有都是 `False` 的情形，也就是所有硬幣都是出現反面的情況。不過那不是個嚴重的問題，可以寫個函數來將同樣的結果變成一種結果，這就留給讀者當作習題。

在這章節中，我們從提出問題到真的寫出型態，並確認這個型態是一個 monad，寫出他的 instance 並實際操作他。這是個很棒的經驗。現在讀者們應該對於 monad 有不少的了解才是。


# ch14


# Zippers 資料結構

![](/files/-LjUmqaBnvtENrGnK7ll)

儘管 Haskell 的純粹性質帶來很多好處，但他讓一些在非純粹語言很容易處理的一些事情變得要用另一種方法解決。由於 referential transparency，同樣一件事在 Haskell 中是沒有分別的。所以如果我們有一個裝滿 5 的樹，而我們希望把其中一個換成 6，那我們必須要知道我們究竟是想改變哪個 5。我們也必須知道我們身處在這棵樹的哪裡。但在 Haskell 中，每個 5 都長得一樣，我們並不能因為他們在記憶體中的位址不同就把他們區分開來。我們也不能改變任何狀態，當我們想要改變一棵樹的時候，我們實際上是說我們要一棵新的樹，只是他長得很像舊的。一種解決方式是記住一條從根節點到現在這個節點的路徑。我們可以這樣表達：給定一棵樹，先往左走，再往右走，再往左走，然後改變你走到的元素。雖然這是可行的，但這非常沒有效率。如果我們想接連改變一個在附近的節點，我們必須再從根節點走一次。在這個章節中，我們會看到我們可以集中注意在某個資料結構上，這樣讓改變資料結構跟遍歷的動作非常有效率。

## 來走二元樹吧!

我們在生物課中學過，樹有非常多種。所以我們來自己發明棵樹吧！

```haskell
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
```

這邊我們的樹不是空的就是有兩棵子樹。來看看一個範例：

```haskell
freeTree :: Tree Char  
freeTree =   
    Node 'P'  
        (Node 'O'  
             (Node 'L'  
              (Node 'N' Empty Empty)  
              (Node 'T' Empty Empty)  
             )  
             (Node 'Y'  
              (Node 'S' Empty Empty)  
              (Node 'A' Empty Empty)  
             )  
        )  
        (Node 'L'  
             (Node 'W'  
                  (Node 'C' Empty Empty)  
                  (Node 'R' Empty Empty)  
             )  
             (Node 'A'  
                  (Node 'A' Empty Empty)  
                  (Node 'C' Empty Empty)  
             )  
        )
```

畫成圖的話就是像這樣：

![](/files/-LjUmqaKYBsRWfKWLO4n)

注意到 `W` 這個節點了嗎？如果我們想要把他變成 `P`。我們會怎麼做呢？一種方式是用 pattern match 的方式做，直到我們找到那個節點為止。要先往右走再往左走，再改變元素內容，像是這樣：

```haskell
changeToP :: Tree Char -> Tree Char  
changeToP (Node x l (Node y (Node _ m n) r)) = Node x l (Node y (Node 'P' m n) r)
```

這不只看起來很醜，而且很不容易閱讀。這到底是怎麼回事？我們使用 pattern match 來拆開我們的樹，我們把 root 綁定成 `x`，把左子樹綁定成 `l`。對於右子樹我們繼續使用 pattern match。直到我們碰到一個子樹他的 root 是 `'W'`。到此為止我們再重建整棵樹，新的樹只差在把 `'W'` 改成了 `'P'`。

有沒有比較好的作法呢？有一種作法是我們寫一個函數，他接受一個樹跟一串 list，裡面包含有行走整個樹時的方向。方向可以是 `L` 或是 `R`，分別代表向左走或向右走。我們只要跟隨指令就可以走達指定的位置：

```haskell
data Direction = L | R deriving (Show)  
type Directions = [Direction]  

changeToP :: Directions-> Tree Char -> Tree Char  
changeToP (L:ds) (Node x l r) = Node x (changeToP ds l) r  
changeToP (R:ds) (Node x l r) = Node x l (changeToP ds r)  
changeToP [] (Node _ l r) = Node 'P' l r
```

如果在 list 中的第一個元素是 `L`，我們會建構一個左子樹變成 `'P'` 的新樹。當我們遞迴地呼叫 `changeToP`，我們只會傳給他剩下的部份，因為前面的部份已經看過了。對於 `R` 的 case 也一樣。如果 list 已經消耗完了，那表示我們已經走到我們的目的地，所以我們就回傳一個新的樹，他的 root 被修改成 `'P'`。

要避免印出整棵樹，我們要寫一個函數告訴我們目的地究竟是什麼元素。

```haskell
elemAt :: Directions -> Tree a -> a  
elemAt (L:ds) (Node _ l _) = elemAt ds l  
elemAt (R:ds) (Node _ _ r) = elemAt ds r  
elemAt [] (Node x _ _) = x
```

這函數跟 `changeToP` 很像，只是他不會記下沿路上的資訊，他只會記住目的地是什麼。我們把 `'W'` 變成 `'P'`，然後用他來查看。

```haskell
ghci> let newTree = changeToP [R,L] freeTree  
ghci> elemAt [R,L] newTree  
'P'
```

看起來運作正常。在這些函數裡面，包含方向的 list 比較像是一種"focus"，因為他特別指出了一棵子樹。一個像 `[R]` 這樣的 list 是聚焦在 root 的右子樹。一個空的 list 代表的是主樹本身。

這個技巧看起來酷炫，但卻不太有效率，特別是在我們想要重複地改變內容的時候。假如我們有一個非常大的樹以及非常長的一串包含方向的 list。我們需要沿著方向從 root 一直走到樹的底部。如果我們想要改變一個鄰近的元素，我們仍需要從 root 開始走到樹的底部。這實在不太令人滿意。

在下一個章節，我們會介紹一個比較好的方法，讓我們可以有效率地改變我們的 focus。

## 凡走過必留下痕跡

![](/files/-LjUmqaMBoMJQeipgti8)

我們需要一個比包含一串方向的 list 更好的聚焦的方法。如果我們能夠在從 root 走到指定地點的沿路上撒下些麵包屑，來紀錄我們的足跡呢？當我們往左走，我們便記住我們選擇了左邊，當我們往右走，便記住我們選擇了右邊。

要找個東西來代表我們的麵包屑，就用一串 `Direction` (他可以是 `L` 或者是 `R`)，只是我們叫他 `BreadCrumb` 而不叫 `Direction`。這是因為現在我們把這串 direction 反過來看了：

```haskell
type Breadcrumbs = [Direction]
```

這邊有一個函數，他接受一棵樹跟一些麵包屑，並在我們往左走時在 list 的前頭加上 `L`

```haskell
goLeft :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs)
goLeft (Node _ l _, bs) = (l, L:bs)
```

我們忽略 root 跟右子樹，直接回傳左子樹以及麵包屑，只是在現有的麵包屑前面加上 `L`。再來看看往右走的函數：

```haskell
goRight :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs)  
goRight (Node _ _ r, bs) = (r, R:bs)
```

幾乎是一模一樣。我們再來做一個先往右走再往左走的函數，讓他來走我們的 `freeTree`

```haskell
ghci> goLeft (goRight (freeTree, []))  
(Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R])
```

![](/files/-LjUmqaO15sq5ngsvapa)

現在我們有了一棵樹，他的 root 是 `'W'`，而他的左子樹的 root 是 `'C'`，右子樹的 root 是 `'R'`。而由於我們先往右走再往左走，所以麵包屑是 `[L,R]`。

要再表示得更清楚些，我們能用定義一個 `-:`

```haskell
x -: f = f x
```

他讓我們可以將值餵給函數這件事反過來寫，先寫值，再來是 `-:`，最後是函數。所以我們可以寫成 `(freeTree, []) -: goRight` 而不是 `goRight (freeTree, [])`。我們便可以把上面的例子改寫地更清楚。

```haskell
ghci> (freeTree, []) -: goRight -: goLeft  
(Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R])
```

### Going back up

如果我們想要往回上走回我們原來的路徑呢？根據留下的麵包屑，我們知道現在的樹是他父親的左子樹，而他的父親是祖父的右子樹。這些資訊並不足夠我們往回走。看起來要達到這件事情，我們除了單純紀錄方向之外，還必須把其他的資料都記錄下來。在這個案例中，也就是他的父親以及他的右子樹。

一般來說，單單一個麵包屑有足夠的資訊讓我們重建父親的節點。所以他應該要包含所有我們沒有選擇的路徑的資訊，並且他應該要紀錄我們沿路走的方向。同時他不應該包含我們現在鎖定的子樹。因為那棵子樹已經在 tuple 的第一個部份中，如果我們也把他紀錄在麵包屑裡，那就會有重複的資訊。

我們來修改一下我們麵包屑的定義，讓他包含我們之前丟掉的資訊。我們定義一個新的型態，而不用 `Direction`：

```haskell
data Crumb a = LeftCrumb a (Tree a) | RightCrumb a (Tree a) deriving (Show)
```

我們用 `LeftCrumb` 來包含我們沒有走的右子樹，而不僅僅只寫個 `L`。我們用 `RightCrumb` 來包含我們沒有走的左子樹，而不僅僅只寫個 `R`。

這些麵包屑包含了所有重建樹所需要的資訊。他們像是軟碟一樣存了許多我們的足跡，而不僅僅只是方向而已。

大致上可以把每個麵包屑想像成一個樹的節點，樹的節點有一個洞。當我們往樹的更深層走，麵包屑攜帶有我們所有走過得所有資訊，只除了目前我們鎖定的子樹。他也必須紀錄洞在哪裡。在 `LeftCrumb` 的案例中，我們知道我們是向左走，所以我們缺少的便是左子樹。

我們也要把 `Breadcrumbs` 的 type synonym 改掉：

```haskell
type Breadcrumbs a = [Crumb a]
```

接著我們修改 `goLeft` 跟 `goRight` 來紀錄一些我們沒走過的路徑的資訊。不像我們之前選擇忽略他。`goLeft` 像是這樣：

```haskell
goLeft :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)
goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
```

你可以看到跟之前版本的 `goLeft` 很像，不只是將 `L` 推到 list 的最前端，我們還加入 `LeftCrumb` 來表示我們選擇向左走。而且我們在 `LeftCrumb` 裡面塞有我們之前走的節點，以及我們選擇不走的右子樹的資訊。

要注意這個函數會假設我們鎖定的子樹並不是 `Empty`。一個空的樹並沒有任何子樹，所以如果我們選擇在一個空的樹中向左走，就會因為我們對 `Node` 做模式匹配而產生錯誤。我們沒有處理 `Empty` 的情況。

`goRight` 也是類似：

```haskell
goRight :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)  
goRight (Node x l r, bs) = (r, RightCrumb x l:bs)
```

在之前我們只能向左或向右走，現在我們由於紀錄了關於父節點的資訊以及我們選擇不走的路的資訊，而獲得向上走的能力。來看看 `goUp` 函數：

```haskell
goUp :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)  
goUp (t, LeftCrumb x r:bs) = (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
```

![](/files/-LjUmqaRsvaNH_UZDkJP)

我們鎖定了 `t` 這棵樹並檢查最新的 `Crumb`。如果他是 `LeftCrumb`，那我們就建立一棵新的樹，其中 `t` 是他的左子樹並用關於我們沒走過得右子樹的資訊來填寫其他 `Node` 的資訊。由於我們使用了麵包屑的資訊來建立父子樹，所以新的 list 移除了我們的麵包屑。

如果我們已經在樹的頂端並使用這個函數的話，他會引發錯誤。等一會我們會用 `Maybe` 來表達可能失敗的情況。

有了 `Tree a` 跟 `Breadcrumbs a`，我們就有足夠的資訊來重建整棵樹，並且鎖定其中一棵子樹。這種方式讓我們可以輕鬆的往上，往左，往右走。這樣成對的資料結構我們叫做 Zipper，因為當我們改變鎖定的時候，他表現得很像是拉鍊一樣。所以我們便定義一個 type synonym:

```haskell
type Zipper a = (Tree a, Breadcrumbs a)
```

我個人是比較傾向於命名成 `Focus`，這樣可以清楚強調我們是鎖定在其中一部分， 至於 Zipper 被更廣泛地使用，所以這邊仍維持叫他做 `Zipper`。

### Manipulating trees under focus

現在我們具備了移動的能力，我們再來寫一個改變元素的函數，他能改變我們目前鎖定的子樹的 root。

```haskell
modify :: (a -> a) -> Zipper a -> Zipper a  
modify f (Node x l r, bs) = (Node (f x) l r, bs)  
modify f (Empty, bs) = (Empty, bs)
```

如果我們鎖定一個節點，我們用 `f` 改變他的 root。如果我們鎖定一棵空的樹，那就什麼也不做。我們可以移來移去並走到我們想要改變的節點，改變元素後並鎖定在那個節點，之後我們可以很方便的移上移下。

```haskell
ghci> let newFocus = modify (\_ -> 'P') (goRight (goLeft (freeTree,[])))
```

我們往左走，然後往右走並將 root 取代為 `'P'`，用 `-:` 來表達的話就是：

```haskell
ghci> let newFocus = (freeTree,[]) -: goLeft -: goRight -: modify (\_ -> 'P')
```

我們也能往上走並置換節點為 `'X'`：

```haskell
ghci> let newFocus2 = modify (\_ -> 'X') (goUp newFocus)
```

如果我們用 `-:` 表達的話：

```haskell
ghci> let newFocus2 = newFocus -: goUp -: modify (\_ -> 'X')
```

往上走很簡單，畢竟麵包屑中含有我們沒走過的路徑的資訊，只是裡面的資訊是相反的，這有點像是要把襪子反過來才能用一樣。有了這些資訊，我們就不用再從 root 開始走一遍，我們只要把反過來的樹翻過來就好，然後鎖定他。

每個節點有兩棵子樹，即使子樹是空的也是視作有樹。所以如果我們鎖定的是一棵空的子樹我們可以做的事就是把他變成非空的，也就是葉節點。

```haskell
attach :: Tree a -> Zipper a -> Zipper a  
attach t (_, bs) = (t, bs)
```

我們接受一棵樹跟一個 zipper，回傳一個新的 zipper，鎖定的目標被換成了提供的樹。我們不只可以用這招把空的樹換成新的樹，我們也能把現有的子樹給換掉。讓我們來用一棵樹換掉我們 `freeTree` 的最左邊：

```haskell
ghci> let farLeft = (freeTree,[]) -: goLeft -: goLeft -: goLeft -: goLeft  
ghci> let newFocus = farLeft -: attach (Node 'Z' Empty Empty)
```

`newFocus` 現在鎖定在我們剛剛接上的樹上，剩下部份的資訊都放在麵包屑裡。如果我們用 `goUp` 走到樹的最上層，就會得到跟原來 `freeTree` 很像的樹，只差在最左邊多了 `'Z'`。

### I'm going straight to top, oh yeah, up where the air is fresh and clean!

寫一個函數走到樹的最頂端是很簡單的：

```haskell
topMost :: Zipper a -> Zipper a  
topMost (t,[]) = (t,[])  
topMost z = topMost (goUp z)
```

如果我們的麵包屑都沒了，就表示我們已經在樹的 root，我們便回傳目前的鎖定目標。晡然，我們便往上走來鎖定到父節點，然後遞迴地呼叫 `topMost`。我們現在可以在我們的樹上四處移動，呼叫 `modify` 或 `attach` 進行我們要的修改。我們用 `topMost` 來鎖定到 root，便可以滿意地欣賞我們的成果。

## 來看串列

Zippers 幾乎可以套用在任何資料結構上，所以聽到他可以被套用在 list 上可別太驚訝。畢竟，list 就是樹，只是節點只有一個兒子，當我們實作我們自己的 list 的時候，我們定義了下面的型態：

```haskell
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
```

![](/files/-LjUmqaTQCAvJ4jz9eVt)

跟我們二元樹的定義比較，我們就可以看出我們把 list 看作樹的原則是正確的。

一串 list 像是 `[1,2,3]` 可以被寫作 `1:2:3:[]`。他由 list 的 head`1` 以及 list 的 tail `2:3:[]` 組成。而 `2:3:[]` 又由 `2` 跟 `3:[]` 組成。至於 `3:[]`，`3` 是 head 而 tail 是 `[]`。

我們來幫 list 做個 zipper。list 改變鎖定的方式分為往前跟往後（tree 分為往上，往左跟往右）。在樹的情形中，鎖定的部份是一棵子樹跟留下的麵包屑。那究竟對於一個 list 而言一個麵包屑是什麼？當我們處理二元樹的時候，我們說麵包屑必須代表 root 的父節點跟其他未走過的子樹。他也必須記得我們是往左或往右走。所以必須要有除了鎖定的子樹以外的所有資訊。

list 比 tree 要簡單，所以我們不需要記住我們是往左或往右，因為我們只有一種方式可以往 list 的更深層走。我們也不需要哪些路徑我們沒有走過的資訊。似乎我們所需要的資訊只有前一個元素。如果我們的 list 是像 `[3,4,5]`，而且我們知道前一個元素是 `2`，我們可以把 `2` 擺回 list 的 head，成為 `[2,3,4,5]`。

由於一個單一的麵包屑只是一個元素，我們不需要把他擺進一個型態裡面，就像我們在做 tree zippers 時一樣擺進 `Crumb`：

```haskell
type ListZipper a = ([a],[a])
```

第一個 list 代表現在鎖定的 list，而第二個代表麵包屑。讓我們寫一下往前跟往後走的函數：

```haskell
goForward :: ListZipper a -> ListZipper a  
goForward (x:xs, bs) = (xs, x:bs)  

goBack :: ListZipper a -> ListZipper a  
goBack (xs, b:bs) = (b:xs, bs)
```

當往前走的時候，我們鎖定了 list 的 tail，而把 head 當作是麵包屑。當我們往回走，我們把最近的麵包屑欻來然後擺到 list 的最前頭。

來看看兩個函數如何運作：

```haskell
ghci> let xs = [1,2,3,4]  
ghci> goForward (xs,[])  
([2,3,4],[1])  
ghci> goForward ([2,3,4],[1])  
([3,4],[2,1])  
ghci> goForward ([3,4],[2,1])  
([4],[3,2,1])  
ghci> goBack ([4],[3,2,1])  
([3,4],[2,1])
```

我們看到在這個案例中麵包屑只不過是一部分反過來的 list。所有我們走過的元素都被丟進麵包屑裡面，所以要往回走很容易，只要把資訊從麵包屑裡面撿回來就好。

這樣的形式也比較容易看出我們為什麼稱呼他為 Zipper，因為他真的就像是拉鍊一般。

如果你正在寫一個文字編輯器，那你可以用一個裝滿字串的 list 來表達每一行文字。你也可以加一個 Zipper 以便知道現在游標移動到那一行。有了 Zipper 你就很容易的可以新增或刪除現有的每一行。

## 陽春的檔案系統

理解了 Zipper 是如何運作之後，我們來用一棵樹來表達一個簡單的檔案系統，然後用一個 Zipper 來增強他的功能。讓我們可以在資料夾間移動，就像我們平常對檔案系統的操作一般。

這邊我們採用一個比較簡化的版本，檔案系統只有檔案跟資料夾。檔案是資料的基本單位，只是他有一個名字。而資料夾就是用來讓這些檔案比較有結構，並且能包含其他資料夾與檔案。所以說檔案系統中的元件不是一個檔案就是一個資料夾，所以我們便用如下的方法定義型態：

```haskell
type Name = String  
type Data = String  
data FSItem = File Name Data | Folder Name [FSItem] deriving (Show)
```

一個檔案是由兩個字串組成，代表他的名字跟他的內容。一個資料夾由一個字串跟一個 list 組成，字串代表名字，而 list 是裝有的元件，如果 list 是空的，就代表他是一個空的資料夾。

這邊是一個裝有些檔案與資料夾的資料夾：

```haskell
myDisk :: FSItem  
    myDisk = 
        Folder "root"   
            [ File "goat_yelling_like_man.wmv" "baaaaaa"  
            , File "pope_time.avi" "god bless"  
            , Folder "pics"  
                [ File "ape_throwing_up.jpg" "bleargh"  
                , File "watermelon_smash.gif" "smash!!"  
                , File "skull_man(scary).bmp" "Yikes!"  
                ]  
            , File "dijon_poupon.doc" "best mustard"  
            , Folder "programs"  
                [ File "fartwizard.exe" "10gotofart"  
                , File "owl_bandit.dmg" "mov eax, h00t"  
                , File "not_a_virus.exe" "really not a virus"  
                , Folder "source code"  
                    [ File "best_hs_prog.hs" "main = print (fix error)"  
                    , File "random.hs" "main = print 4"  
                    ]  
                ]  
            ]
```

這就是目前我的磁碟的內容。

### A zipper for our file system

![](/files/-LjUmqaVNKMS0jQRJl4i)

我們有了一個檔案系統，我們需要一個 Zipper 來讓我們可以四處走動，並且增加、修改或移除檔案跟資料夾。就像二元樹或 list，我們會用麵包屑留下我們未走過路徑的資訊。正如我們說的，一個麵包屑就像是一個節點，只是他包含所有除了我們現在正鎖定的子樹的資訊。

在這個案例中，一個麵包屑應該要像資料夾一樣，只差在他缺少了我們目前鎖定的資料夾的資訊。為什麼要像資料夾而不是檔案呢？因為如果我們鎖定了一個檔案，我們就沒辦法往下走了，所以要留下資訊說我們是從一個檔案走過來的並沒有道理。一個檔案就像是一棵空的樹一樣。

如果我們鎖定在資料夾 `"root"`，然後鎖定在檔案 `"dijon_poupon.doc"`，那麵包屑裡的資訊會是什麼樣子呢？他應該要包含上一層資料夾的名字，以及在這個檔案前及之後的所有項目。我們要的就是一個 `Name` 跟兩串 list。藉由兩串 list 來表達之前跟之後的元素，我們就完全可以知道我們目前鎖定在哪。

來看看我們麵包屑的型態：

```haskell
data FSCrumb = FSCrumb Name [FSItem] [FSItem] deriving (Show)
```

這是我們 Zipper 的 type synonym：

```haskell
type FSZipper = (FSItem, [FSCrumb])
```

要往上走是很容易的事。我們只要拿現有的麵包屑來組出現有的鎖定跟麵包屑：

```haskell
fsUp :: FSZipper -> FSZipper  
fsUp (item, FSCrumb name ls rs:bs) = (Folder name (ls ++ [item] ++ rs), bs)
```

由於我們的麵包屑有上一層資料夾的名字，跟資料夾中之前跟之後的元素，要往上走不費吹灰之力。

至於要往更深層走呢？如果我們現在在 `"root"`，而我們希望走到 `"dijon_poupon.doc"`，那我們會在麵包屑中留下 `"root"`，在 `"dijon_poupon.doc"` 之前的元素，以及在他之後的元素。

這邊有一個函數，給他一個名字，他會鎖定在在現有資料夾中的一個檔案：

```haskell
import Data.List (break)  

fsTo :: Name -> FSZipper -> FSZipper  
fsTo name (Folder folderName items, bs) =   
  let (ls, item:rs) = break (nameIs name) items  
  in  (item, FSCrumb folderName ls rs:bs)  

nameIs :: Name -> FSItem -> Bool  
nameIs name (Folder folderName _) = name == folderName  
nameIs name (File fileName _) = name == fileName
```

`fsTo` 接受一個 `Name` 跟 `FSZipper`，回傳一個新的 `FSZipper` 鎖定在某個檔案上。那個檔案必須在現在身處的資料夾才行。這函數不會四處找尋這檔案，他只會看現在的資料夾。

![](/files/-LjUmqaXHddAHwqZ5OlH)

首先我們用 `break` 來把身處資料夾中的檔案們分成在我們要找的檔案前的，跟之後的。如果記性好，`break` 會接受一個 predicate 跟一個 list，並回傳兩個 list 組成的 pair。第一個 list 裝有 predicate 會回傳 `False` 的元素，而一旦碰到一個元素回傳 `True`，他就把剩下的所有元素都放進第二個 list 中。我們用了一個輔助函數叫做 `nameIs`，他接受一個名字跟一個檔案系統的元素，如果名字相符的話他就會回傳 `True`。

現在 `ls` 一個包含我們要找的元素之前元素的 list。`item` 就是我們要找的元素，而 `rs` 是剩下的部份。有了這些，我們不過就是把 `break` 傳回來的東西當作鎖定的目標，來建造一個麵包屑來包含所有必須的資訊。

如果我們要找的元素不在資料夾中，那 `item:rs` 這個模式會符合到一個空的 list，便會造成錯誤。如果我們現在的鎖定不是一個資料夾而是一個檔案，我們也會造成一個錯誤而讓程式當掉。

現在我們有能力在我們的檔案系統中移上移下，我們就來嘗試從 root 走到 `"skull_man(scary).bmp"` 這個檔案吧：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsTo "skull_man(scary).bmp"
```

`newFocus` 現在是一個鎖定在 `"skull_man(scary).bmp"` 的 Zipper。我們把 zipper 的第一個部份拿出來看看：

```haskell
ghci> fst newFocus  
File "skull_man(scary).bmp" "Yikes!"
```

我們接著往上移動並鎖定在一個鄰近的檔案 `"watermelon_smash.gif"`：

```haskell
ghci> let newFocus2 = newFocus -: fsUp -: fsTo "watermelon_smash.gif"  
ghci> fst newFocus2  
File "watermelon_smash.gif" "smash!!"
```

### Manipulating our file system

現在我們知道如何遍歷我們的檔案系統，因此操作也並不是難事。這邊便來寫個重新命名目前鎖定檔案或資料夾的函數：

```haskell
fsRename :: Name -> FSZipper -> FSZipper  
fsRename newName (Folder name items, bs) = (Folder newName items, bs)  
fsRename newName (File name dat, bs) = (File newName dat, bs)
```

我們可以重新命名 `"pics"` 資料夾為 `"cspi"`：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsRename "cspi" -: fsUp
```

我們走到 `"pics"` 這個資料夾，重新命名他然後再往回走。

那寫一個新的元素在我們目前的資料夾呢？

```haskell
fsNewFile :: FSItem -> FSZipper -> FSZipper  
fsNewFile item (Folder folderName items, bs) =   
    (Folder folderName (item:items), bs)
```

注意這個函數會沒辦法處理當我們在鎖定在一個檔案卻要新增元素的情況。

現在要在 `"pics"` 資料夾中加一個檔案然後走回 root：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsNewFile (File "heh.jpg" "lol") -: fsUp
```

當我們修改我們的檔案系統，他不會真的修改原本的檔案系統，而是回傳一份新的檔案系統。這樣我們就可以存取我們舊有的系統（也就是 `myDisk`）跟新的系統（`newFocus` 的第一個部份）使用一個 Zippers，我們就能自動獲得版本控制，代表我們能存取到舊的資料結構。這也不僅限於 Zippers，也是由於 Haskell 的資料結構有 immutable 的特性。但有了 Zipper，對於操作會變得更容易，我們可以自由地在資料結構中走動。

## 小心每一步

到目前為止，我們並沒有特別留意我們在走動時是否會超出界線。不論資料結構是二元樹，List 或檔案系統。舉例來說，我們的 `goLeft` 函數接受一個二元樹的 Zipper 並鎖定到他的左子樹：

```haskell
goLeft :: Zipper a -> Zipper a  
goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
```

![](/files/-LjUmqaZq3dXfr69aUwB)

但如果我們走的樹其實是空的樹呢？也就是說，如果他不是 `Node` 而是 `Empty`？再這情況，我們會因為模式匹配不到東西而造成 runtime error。我們沒有處理空的樹的情形，也就是沒有子樹的情形。到目前為止，我們並沒有試著在左子樹不存在的情形下鎖定左子樹。但要走到一棵空的樹的左子樹並不合理，只是到目前為止我們視而不見而已。

如果我們已經在樹的 root 但仍舊試著往上走呢？這種情形也同樣會造成錯誤。。用了 Zipper 讓我們每一步都好像是我們的最後一步一樣。也就是說每一步都有可能會失敗。這讓你想起什麼嗎？沒錯，就是 Monad。更正確的說是 `Maybe` monad，也就是有可能失敗的 context。

我們用 `Maybe` monad 來加入可能失敗的 context。我們要把原本接受 Zipper 的函數都改成 monadic 的版本。首先，我們來處理 `goLeft` 跟 `goRight`。函數的失敗有可能反應在他們的結果，這個情況也不利外。所以來看下面的版本：

```haskell
goLeft :: Zipper a -> Maybe (Zipper a)  
goLeft (Node x l r, bs) = Just (l, LeftCrumb x r:bs)  
goLeft (Empty, _) = Nothing  

goRight :: Zipper a -> Maybe (Zipper a)  
goRight (Node x l r, bs) = Just (r, RightCrumb x l:bs)  
goRight (Empty, _) = Nothing
```

然後我們試著在一棵空的樹往左走，我們會得到 `Nothing`:

```haskell
ghci> goLeft (Empty, [])  
Nothing  
ghci> goLeft (Node 'A' Empty Empty, [])  
Just (Empty,[LeftCrumb 'A' Empty])
```

看起來不錯。之前的問題是我們在麵包屑用完的情形下想往上走，那代表我們已經在樹的 root。如果我們不注意的話那 `goUp` 函數就會丟出錯誤。

```haskell
goUp :: Zipper a -> Zipper a  
goUp (t, LeftCrumb x r:bs) = (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
```

我們改一改讓他可以失敗得好看些：

```haskell
goUp :: Zipper a -> Maybe (Zipper a)  
goUp (t, LeftCrumb x r:bs) = Just (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = Just (Node x l t, bs)  
goUp (_, []) = Nothing
```

如果我們有麵包屑，那我們就能成功鎖定新的節點，如果沒有，就造成一個失敗。

之前這些函數是接受 Zipper 並回傳 Zipper，這代表我們可以這樣操作：

```haskell
gchi> let newFocus = (freeTree,[]) -: goLeft -: goRight
```

但現在我們不回傳 `Zipper a` 而回傳 `Maybe (Zipper a)`。所以沒辦法像上面串起來。我們在之前章節也有類似的問題。他是每次走一步，而他的每一步都有可能失敗。

幸運的是我們可以從之前的經驗中學習，也就是使用 `>>=`，他接受一個有 context 的值（也就是 `Maybe (Zipper a)`），會把值餵進函數並保持其他 context 的。所以就像之前的例子，我們把 `-:` 換成 `>>=`。

```haskell
ghci> let coolTree = Node 1 Empty (Node 3 Empty Empty)  
ghci> return (coolTree,[]) >>= goRight  
Just (Node 3 Empty Empty,[RightCrumb 1 Empty])  
ghci> return (coolTree,[]) >>= goRight >>= goRight  
Just (Empty,[RightCrumb 3 Empty,RightCrumb 1 Empty])  
ghci> return (coolTree,[]) >>= goRight >>= goRight >>= goRight  
Nothing
```

我們用 `return` 來把 Zipper 放到一個 `Just` 裡面。然後用 `>>=` 來餵到 `goRight` 的函數中。首先我們做了一棵樹他的左子樹是空的，而右邊是有兩顆空子樹的一個節點。當我們嘗試往右走一步，便會得到成功的結果。往右走兩步也還可以，只是會鎖定在一棵空的子樹上。但往右走三步就沒辦法了，因為我們不能在一棵空子樹上往右走，這也是為什麼結果會是 `Nothing`。

現在我們具備了安全網，能夠在出錯的時候通知我們。

我們的檔案系統仍有許多情況會造成錯誤，例如試著鎖定一個檔案，或是不存在的資料夾。剩下的就留作習題。


# Resource

網路上 Haskell 的資源雖不少，但由於目前社群的人力有限。所以比較沒能整理成一套能循序漸進的學習方式。常常會在 Haskell Wiki 上撞到對初學者太過於深入的東西。或是覺得奇怪怎麼不斷有之前沒看過的東西冒出來。造成學習 Haskell 很大的撞牆期。這邊譯者會漸漸補充一些自己覺得有用的資源，嘗試找到一些中階的教材能夠銜接初學跟進階。

## Specification

* [Haskell 98 Report](http://www.haskell.org/onlinereport/):  Haskell 的標準，目前 GHC 如果不用任何 Extension，寫出來的程式是符合 Haskell 98 的標準。
* [Haskell 2010 Report](http://www.haskell.org/onlinereport/haskell2010/):，最新的標準，有許多已經實作但要開 Extension 才能用。

## Tools

* [Hoogle](http://www.haskell.org/hoogle/): Haskell 函數的搜尋引擎，不只可以用函數的名稱搜尋，也可以用函數的型態來搜尋。
* [Hayoo](http://holumbus.fh-wedel.de/hayoo/hayoo.html): 跟 Hoogle 同樣功能。
* [hdiff](http://hdiff.luite.com/): 可以方便查詢 package 不同版號之間的差異。
* [packdeps](http://packdeps.haskellers.com/): 方便查詢 Hackage 上面 package 之間的相依性。

## Lectures & Articles

* [Wikibook Haskell](http://en.wikibooks.org/wiki/Haskell): 豐富的 Wikibook 資源
* [CS240h](http://www.scs.stanford.edu/11au-cs240h/notes/): David Mazières 跟 Bryan O'Sullivan 在 Stanford 開的課。
* [本物のプログラマはHaskellを使う](http://itpro.nikkeibp.co.jp/article/COLUMN/20060915/248215/): Haskell 專欄
* [Write Yourself a Scheme in 48 Hours](http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours)，Audrey Tang 寫的教學，教你如何用 Haskell 寫出一個 Scheme。
* \[德國大學的 Functional Programming 課程，語言是用 FP （英文授課）]\(<http://video.s-inf.de/*FP.2005-SS-Giesl.(COt).HD_Videoaufzeichnung>)&#x20;
* [Simon Marlow 講解 parallel haskell 的投影片](http://community.haskell.org/~simonmar/slides/cadarache2012/)&#x20;
* [FLOLAC 2012](http://flolac.iis.sinica.edu.tw/flolac12/doku.php?id=zh-tw:start)
* [ICFP 2012](http://www.youtube.com/channel/UCP9g4dLR7xt6KzCYntNqYcw?\&desktop_uri=%2Fchannel%2FUCP9g4dLR7xt6KzCYntNqYcw)
* [Explanation of Generalized Algebraic Data Types](http://archive.org/details/ExplanationOfGeneralizedAlgebraicDataTypesgadts)
* [A Quick Intro to Snap](http://bonus500.github.com/sc2blog/*title-slide)
* [Logic, Languages, Compilation, and Verification 2012](http://www.cs.uoregon.edu/Research/summerschool/summer12/curriculum.html)
* [Haskell in Halle/Saale](http://iba-cg.de/hal7.html)
* \[Fast Code Nation]\(<http://bos.github.com/reaktor-dev-day-2012/reaktor-talk-slides.html*(1>))

## Forum

* [Stackoverflow](http://stackoverflow.com/questions/tagged/haskell): 著名 stackoverflow 上的\*haskell tag
* [Reddit](http://www.reddit.com/r/haskell/)

## Online Judge

* [H-99: Ninety-Nine Haskell Problems](http://www.haskell.org/haskellwiki/99_questions)&#x20;
* [Project Euler](http://projecteuler.net/): 已經算非常著名的 Online Judge，可惜只有上傳答案。如果問題實在想不出來，Haskell Wiki 上也有參考答案。
* [SPOJ](http://www.spoj.pl/): 少數的 Online Judge 系統可以上傳 Haskell 的，題目非常豐富。也是練 ACM ICPC 常用的網站。

## Books

* [Learn you a Haskell for great good (Japanese Translation)](http://www.amazon.co.jp/%E4%81%99%E3%81%94%E3%81%84Haskell%E3%81%9F%E3%81%AE%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%BC%E3%81%86-Miran-Lipova%C4%8Da/dp/4274068854)
* [Real World Haskell](http://book.realworldhaskell.org/)
* [Yesod Book, 講解如何使用 Yesod Web Framework](http://www.yesodweb.com/book)

## PL Researchers

* [穆信成老師](http://www.iis.sinica.edu.tw/~scm/)
* [單中杰老師](http://www.cs.rutgers.edu/~ccshan/)
* [Conal Elliott](http://conal.net/)
* [Edward Yang](http://blog.ezyang.com/)
* [Edward Kmett](http://comonad.com/reader/)

## Interesting Projects

* [Fay Programming Langauge](http://fay-lang.org/) 用 Haskell 語言的子集，直接轉譯成 Javascript
* [Leksah](http://leksah.org/): Haskell IDE
* [Super Manao Bros](https://github.com/Mokehehe/Monao/): 超級瑪利歐

## Taiwan Functional Programming User Group

* [TW-FPUG on Vimeo](http://vimeo.com/groups/140878)
* [Haskell 進階運算元件介紹](https://docs.google.com/file/d/0BzqwG7n2gs71blE5V2pzR29WUDQ/edit?pli=1)


# resource


# faq


# FAQ

![](https://github.com/mno2/learnyouahaskell-zh/tree/689e7bcf954b5c0011bf6a515895f6c9a4706017/zh-tw/faq/$img/turtle.png)

## 我能把這份教學放在我的網站嗎？我可以更改裡面的內容嗎？

當然。它受[creative commons attribution noncommercial blah blah blah...](http://creativecommons.org/licenses/by-nc-sa/3.0/)許可證的保護，因此你可以分享或修改它。只要你是發自內心想要這麼作，並且只將教學用於非商業目的。

## 推薦其它幾個 Haskell 讀物？

有很多出色的教學，但我得告訴你[Real World Haskell](http://book.realworldhaskell.org/read/)出類拔萃。它太棒了。我覺得它可以和這個教程搭配着一起讀。這個教程提供幾個簡單例子好讓初學者對那點概念有個簡單認識，而 Real World Haskell 真正教給你如何充分地使用它們。

## 我怎麼聯絡到你？

最好的方式是用寄封 email 給我，email 是 bonus at learnyouahaskell dot com。不過有點耐心，我沒有 24 小時在電腦旁，所以如果我沒有即時地回覆你的 email 的話，請多包含。

## 我想要一些習題！

很快就會有了！很多人不斷來問我能不能加些習題，我正在趕工呢。

## 關於作者

我的名字是 Miran Lipovača，住在斯洛文尼亞的 Ljubljana。 大部分時間我都閒閒沒事，不過當我認真的時候我不是在寫程式，繪畫，練格鬥技，或彈 bass。 我甚至弄了一個討論 bass 的網站。 我甚至蒐集一堆貓頭鷹玩偶，有時候我會對他們自言自語。

## 關於簡體譯者

我的名字是[Fleurer](http://fleurer-lee.com)，在淄博的山東理工大學讀書。電子郵件是 me.ssword at gmail dot com

## 關於繁體譯者

我是[MnO2](http://blog.mno2.org)，Haskell 愛好者，


# HASKELL 趣学指南

此书为 LEARN YOU A HASKELL FOR GREAT GOOD 的 CC 中文翻译。最早是由 fleuria 摆在网路上前几章的简体翻译，有 MnO2 接手并把后半部分翻译完成。之后在网路上集结众多贡献者们维护的版本。

由于是借助大家热情而无偿产出的书籍，难免有所错误。如果有看到错误还请 send pull request。原本的第一版本的 gitbook 并没有跟 github integration 自动发布修正的错误，需要人力 merge 并 push 因此耽误了不少。新的版本的 gitbook 就有 github integration，PR merge 后就能自动发布。


# SUMMARY

* [Chap 01 - Introduction](/zh-cn/ch01/introduction)
* [Chap 02 - Ready Go](/zh-cn/ch02/ready-go)
* [Chap 03 - Type And Typeclass](/zh-cn/ch03/type-and-typeclass)
* [Chap 04 - Syntax in Function](/zh-cn/ch04/syntax-in-function)
* [Chap 05 - Recursion](/zh-cn/ch05/recursion)
* [Chap 06 - High Order Function](/zh-cn/ch06/high-order-function)
* [Chap 07 - Module](/zh-cn/ch07/module)
* [Chap 08 - Build Our Own Type and Typeclass](/zh-cn/ch08/build-our-own-type-and-typeclass)
* [Chap 09 - Input and Output](/zh-cn/ch09/input-and-output)
* [Chap 10 - Functionally Solving Problems](/zh-cn/ch10/functionally-solving-problems)
* [Chap 11 - Functors, Applicative Functors 與 Monoids](/zh-cn/ch11/functors-applicative-functors-and-monoids)
* [Chap 12 - A Fistful of Monad](/zh-cn/ch12/a-fistful-of-monads)
* [Chap 13 - For a Few Monad More](/zh-cn/ch13/for-a-few-monads-more)
* [Chap 14 - Zippers](/zh-cn/ch14/zippers)
* [FAQ](/zh-cn/faq/faq)
* [Resource](/zh-cn/resource/resource)


# ch01


# 简介

## 关于这份教学

欢迎来到 *Haskell 趣学指南*！会想看这篇文章表示你对学习 Haskell 有很大的兴趣。你来对地方了，来让我简单介绍一下这个教学。

撰写这份教学，一方面是让我自己对 Haskell 更熟练，另一方面是希望能够分享我的学习经验，帮助初学者更快进入状况。网络上已经有无数 Haskell 的教学文档，在我学习的过程中，我并不限于只参考一份来源。我常常阅读不同的教学文章，他们每个都从不同的角度出发。参考这些资源让我能将知识化整为零。这份教学是希望提供更多的机会能让你找到你想要得到的解答。

![](/files/-LjUmrkW5Hh1iMs_baon)

这份教学主要针对已经有使用命令式编程语言 (imperative programming languages) 写程序经验 (C, C++, Java, Python …) 、却未曾接触过函数式编程语言 (functional programming languages) (Haskell, ML, OCaml …) 的读者。就算没有写程序经验也没关系，会想学 Haskell 的人我相信都是很聪明的。

若在学习中遇到什么地方不懂的，Freenode IRC 上的 #Haskell 频道是提问的绝佳去处。那里的人都很友善，有耐心且能体谅初学者。 (译注： Stackoverflow 上的 #haskell tag 也有很多 Haskell 神人们耐心地回答问题，提供给不习惯用 IRC 的人的另一个选择。)

我经历了不少挫折才学会 Haskell，在初学的时候它看起来是如此奇怪的语言。但有一天我突然开窍了，之后的学习便如鱼得水。我想要表达的是：尽管 Haskell 乍看下如此地诡异，但假如你对编程十分有兴趣，他非常值得你学习。学习 Haskell 让你想起你第一次写程序的感觉。非常有趣，而且强迫你 Think different。

## 什么是 Haskell？

![](/files/-LjUmrkYoJxDGSDbqFB9)

Haskell 与其他语言不同，是一门*纯粹函数式编程语言 (purely functional programming language)*。在一般常见的命令式语言中，要执行操作的话是给电脑一组命令，而状态会随着命令的执行而改变。例如你指派变量 `a` 的值为 5，而随后做了其它一些事情之后 a 就可能变成的其它值。有控制流程 (control flow)，你就可以重复执行操作。然而在纯粹函数式编程语言中，你不是像命令式语言那样命令电脑「要做什么」，而是通过用函数来描述出问题「是什么」，如「*阶乘是指从1到某个数的乘积」，「一个串列中数字的和」是指把第一个数字跟剩余数字的和相加。你用宣告函数是什么的形式来写程序。另外，变量 (variable) 一旦被指定，就不可以更改了，你已经说了 `a` 就是 5，就不能再另说 a 是别的什么数。（译注：其实用 variable 来表达造成字义的 overloading，会让人联想到 imperative languages 中 variable 是代表状态，但在 functional languages 中 variable 是相近于数学中使用的 variable。`x=5` 代表 `x` 就是 5，不是说 `x` 在 5 这个状态。) 所以说，在纯粹函数式编程语言中的函数能做的唯一事情就是利用引数计算结果，不会产生所谓的"副作用 (side effect)" (译注：也就是改变非函数内部的状态，像是 imperative languages 里面动到 global variable 就是 side effect)。一开始会觉得这限制很大，不过这也是他的优点所在：若以同样的参数调用同一个函数两次，得到的结果一定是相同。这被称作“*&#x5F15;用透明 (Referential Transparency)\_” (译注：这就跟数学上函数的使用一样)。如此一来编译器就可以理解程序的行为，你也很容易就能验证一个函数的正确性，继而可以将一些简单的函数组合成更复杂的函数。

![](/files/-LjUmrk_oVYC8o8TQH5U)

Haskell 是*惰性 (lazy)* 的。也就是说若非特殊指明，函数在真正需要结果以前不会被求值。再加上引用透明，你就可以把程序仅看作是数据的一系列变形。如此一来就有了很多有趣的特性，如无限长度的数据结构。假设你有一个 List: `xs = [1,2,3,4,5,6,7,8]`，还有一个函数 `doubleMe`，它可以将一个 List 中的所有元素都乘以二，返回一个新的 List。若是在命令式语言中，把一个 List 乘以 8，执行 `doubleMe(doubleMe(doubleMe(xs)))`，得遍历三遍 `xs` 才会得到结果。而在惰性语言中，调用 `doubleMe` 时并不会立即求值，它会说“嗯嗯，待会儿再做！”。不过一旦要看结果，第一个 `doubleMe` 就会对第二个说“给我结果，快！”第二个 `doubleMe` 就会把同样的话传给第三个 `doubleMe`，第三个 `doubleMe` 只能将 1 乘以 2 得 2 后交给第二个，第二个再乘以 2 得 4 交给第一个，最终得到第一个元素 8。也就是说，这一切只需要遍历一次 list 即可，而且仅在你真正需要结果时才会执行。惰性语言中的计算只是一组初始数据和变换公式。

![](/files/-LjUmrkbBoGkHoUrvhUS)

Haskell 是*静态类型 (statically typed)* 的。当你编译程序时，编译器需要明确哪个是数字，哪个是字串。这就意味着很大一部分错误都可以在编译时被发现，若试图将一个数字和字串相加，编译器就会报错。Haskell 拥有一套强大的类型系统，支持自动类型推导 (type inference)。这一来你就不需要在每段代码上都标明它的类型，像计算 `a=5+4`，你就不需另告诉编译器“ a 是一个数值”，它可以自己推导出来。类型推导可以让你的程序更加简练。假设有个函数是将两个数值相加，你不需要声明其类型，这个函数可以对一切可以相加的值进行计算。

Haskell 采纳了很多高端编程语言的概念，因而它的代码*优雅且简练*。与同层次的命令式语言相比，Haskell 的代码往往会更短，更短就意味着更容易理解，bug 也就更少。

Haskell 这语言是一群非常聪明的人设计的 (他们每个人都有 PhD 学位)。最初的工作始于 1987 年，一群学者聚在一起想设计一个屌到爆的编程语言。到了 2003 年，他们公开了 Haskell Report，这份报告描述了 Haskell 语言的一个稳定版本。(译注：这份报告是 Haskell 98 标准的修订版，Haskell 98 是在 1999 年公开的，是目前 Haskell 各个编译器实现缺省支持的标准。在 2010 年又公开了另一份 Haskell 2010 标准，详情可见穆信成老师所撰写的[简介](http://www.iis.sinica.edu.tw/~scm/ncs/2010/07/haskell-2010-report/)。

## 要使用 Haskell 有哪些要求呢？

一句话版本的答案是：你只需要一个编辑器和一个编译器。在这里我们不会对编辑器多加着墨，你可以用任何你喜欢的编辑器。至于编译器，在这份教学中我们会使用目前最流行的版本：GHC。而安装 GHC 最方便的方法就是去下载 Haskell Platform，他包含了许多现成 Runtime Library 让你方便写程序。(译注：Ubuntu 的用户有现成的套件可以使用，可以直接 `apt-get install Haskell-platform` 来安装。但套件的版本有可能比较旧。)

GHC 可以解释执行 Haskell Script （通常是以 `.hs` 作为结尾），也可以编译。它还有个交互模式，你可以在里面调用 Script 里定义的函数，即时得到结果。 对于学习而言，这比每次修改都编译执行要方便的多。想进入交互模式，只要打开控制台输入 `ghci` 即可。假设你在 `myfunctions.hs` 里定义了一些函数，在 ghci 中输入 `:l myfunctions.hs`，ghci 便会加载 `myfunctions.hs`。之后你便可以调用你定义的函数。一旦修改了这个 `.hs` 文件的内容，再次执行 `:l myfunctions.hs` 或者相同作用的 `:r` ，都可以重新加载该文件。我自己通常就是在 `.hs` 文件中定义几个函数，再到 ghci 加载，调用看看，再修改再重新加载。这也正是我们往后的基本流程。


# ch02


# 从零开始

## 准备好了吗？

![](/files/55MfjaUN96xy0vqhuydr)

准备来开始我们的旅程！如果你就是那种从不看说明书的人，我推荐你还是回头看一下简介的最后一节。那里面讲了这个教学中你需要用到的工具及基本用法。我们首先要做的就是进入 ghc 的交互模式，接着就可以写几个函数体验一下 Haskell 了。打开终端机，输入 `ghci`，你会看到下列欢迎消息：

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

恭喜您已经进入了 ghci 了！目前它的命令行提示是 `prelude>`，不过它在你装载一些模块之后会变的比较长。为了美观起见，我们会输入指令 `:set prompt "ghci> "` 把它改成 `ghci>`。

首先来看一些简单的运算

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

很简单吧！你也可以在一行中使用多个运算子，他们会按照运算子优先级执行计算，而使用括号可以改变执行的优先级。

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

但注意处理负数的时候有个小陷阱：我们执行 `5 * -3` 会 ghci 会回报错误。所以说，使用负数时最好将其置于括号之中，像 `5*(-3)` 就不会有问题。

要进行布尔代数 (Boolean Algebra) 的演算也是很直觉的。你也许早就会猜，`&&` 指的是布尔代数上的 AND，而 `||` 指的是布尔代数上的 OR，`not` 会把 `True` 变成 `False`，`False` 变成 `True`。

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

相等性可以这样判定

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

那执行 `5+"llama"` 或者 `5==True` 会怎样？ 如果我们真的试着在 ghci 中跑，会得到下列的错误消息：

```haskell
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"` 并不是数值型别，所以它不知道该怎样才能给它加上 5。即便是 `"four"` 甚至是 `“4”` 也不可以，Haskell 不拿它当数值。执行 `True==5`, ghci 就会提示型别不匹配。`+` 运算子要求两端都是数值，而 `==` 运算子仅对两个可比较的值可用。这就要求他们的型别都必须一致，苹果和橘子就无法做比较。我们会在后面深入地理解型别的概念。Note: `5+4.0` 是可以执行的，5 既可以做被看做整数也可以被看做浮点数，但 4.0 则不能被看做整数。

![](/files/W9OFoBRoYQ38bGL8TCYR)

也许你并未察觉，不过从始至终我们一直都在使用函数。`*` 就是一个将两个数相乘的函数，就像三明治一样，用两个参数将它夹在中央，这被称作中缀函数。而其他大多数不能与数夹在一起的函数则被称作前缀函数。绝大部分函数都是前缀函数，在接下来我们就不多做区别。大多数命令式编程语言中的函数调用形式通常就是函数名，括号，由逗号分隔的参数表。而在 Haskell 中，函数调用的形式是函数名，空格，空格分隔的参数表。简单举个例子，我们调用 Haskell 中最无趣的函数：

```haskell
ghci> succ 8
9
```

`succ` 函数返回一个数的后继 (successor)。而且如你所见，在 Haskell 中是用空格来将函数与参数分隔的。至于调用多个参数的函数也很容易，`min` 和 `max` 接受两个可比较大小的参数，并返回较大或者较小的那个数。

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

函数调用拥有最高的优先级，如下两句是等效的

```haskell
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。

```haskell
doubleMe x = x + x
```

函数的声明与它的调用形式大致相同，都是先函数名，后跟由空格分隔的参数表。但在声明中一定要在 `=` 后面定义函数的行为。

保存为 `baby.hs` 或任意名称，然后转至保存的位置，打开 ghci，执行 `:l baby.hs`。这样我们的函数就装载成功，可以调用了。

```haskell
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
```

`+` 运算子对整数和浮点都可用(实际上所有有数字特征的值都可以)，所以我们的函数可以处理一切数值。声明一个包含两个参数的函数如下：

```haskell
doubleUs x y = x*2 + y*2
```

很简单。将其写成 `doubleUs x y = x + x + y + y` 也可以。测试一下(记住要保存为 `baby.hs` 并到 ghci 下边执行 `:l baby.hs`)

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

你可以在其他函数中调用你编写的函数，如此一来我们可以将 `doubleUs` 函数改为：

```haskell
doubleUs x y = doubleMe x + doubleMe y
```

![](/files/TW2NE05S3aTf0RXiz43V)

这种情形在 Haskell 下边十分常见：编写一些简单的函数，然后将其组合，形成一个较为复杂的函数，这样可以减少重复工作。设想若是哪天有个数学家验证说 2 应该是 3，我们只需要将 `doubleMe` 改为 `x+x+x` 即可，由于 `doubleUs` 调用到 `doubleMe`，于是整个程序便进入了 2 即是 3 的古怪世界。

Haskell 中的函数并没有顺序，所以先声明 `doubleUs` 还是先声明 `doubleMe` 都是同样的。如下，我们编写一个函数，它将小于 100 的数都乘以 2，因为大于 100 的数都已经足够大了！

```haskell
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，可以如此修改：

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

若是去掉括号，那就会只在小于 100 的时候加 1。注意函数名最后的那个单引号，它没有任何特殊含义，只是一个函数名的合法字符罢了。通常，我们使用单引号来区分一个稍经修改但差别不大的函数。定义这样的函数也是可以的：

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

在这里有两点需要注意。首先就是我们没有大写 `conan` 的首字母，因为首字母大写的函数是不允许的，稍后我们将讨论其原因；另外就是这个函数并没有任何参数。没有参数的函数通常被称作“定义”(或者“名字”)，一旦定义，`conanO'Brien` 就与字串 `"It's a-me, Conan O'Brien!"` 完全等价，且它的值不可以修改。

## List 入门

![](/files/1JkU705jnLA1rBwQnQog)

在 Haskell 中，List 就像现实世界中的购物单一样重要。它是最常用的数据结构，并且十分强大，灵活地使用它可以解决很多问题。本节我们将对 List，字串和 list comprehension 有个初步了解。 在 Haskell 中，List 是一种单型别的数据结构，可以用来存储多个型别相同的元素。我们可以在里面装一组数字或者一组字符，但不能把字符和数字装在一起。

```
*Note*: 在 ghci 下，我们可以使用 ``let`` 关键字来定义一个常量。在 ghci 下执行 ``let a=1`` 与在脚本中编写 ``a=1`` 是等价的。
```

```haskell
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 合并是很常见的操作，这可以通过 `++` 运算子实现。

```haskell
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 前端插入元素会是更好的选择。

```haskell
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。

```haskell
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：

```haskell
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 的大小。它会先比较第一个元素，若它们的值相等，则比较下一个，以此类推。

```haskell
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 的首个元素。

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

**tail** 返回一个 List 的尾部，也就是 List 除去头部之后的部分。

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

**last** 返回一个 List 的最后一个元素。

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

**init** 返回一个 List 除去最后一个元素的部分。

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

如果我们把 List 想象为一头怪兽，那这就是它的样子：

![](/files/FsvSe4VEa0VF4o2mqLij)

试一下，若是取一个空 List 的 `head` 又会怎样？

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

糟糕，程序直接跳出错误。如果怪兽都不存在的话，那他的头也不会存在。在使用 `head`，`tail`，`last` 和 `init` 时要小心别用到空的 List 上，这个错误不会在编译时被捕获。所以说做些工作以防止从空 List 中取值会是个好的做法。

**length** 返回一个 List 的长度。

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

**null** 检查一个 List 是否为空。如果是，则返回 `True`，否则返回 `False`。应当避免使用 `xs==[]` 之类的语句来判断 List 是否为空，使用 null 会更好。

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

**reverse** 将一个 List 反转:

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

**take** 返回一个 List 的前几个元素，看：

```haskell
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 中的前几个元素。

```haskell
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` 返回最小的。

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

**sum** 返回一个 List 中所有元素的和。`product` 返回一个 List 中所有元素的积。

```haskell
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，通常以中缀函数的形式调用它。

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

这就是几个基本的 List 操作函数，我们会在往后的一节中了解更多的函数。

## 使用 Range

![](/files/Nu2SKnZ0zXSkFm6S4GU8)

今天如果想得到一个包含 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 那就铁定是个笨方法。

```haskell
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 间所有的偶数或者 3 的倍数该怎样？

```haskell
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 中使用浮点数要格外小心！出于定义的原因，浮点数并不精确。若是使用浮点数的话，你就会得到如下的糟糕结果

```haskell
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 。如果你只是想看一下它的运算结果而已，它会运行个没完的。所以应该在某处划好范围。

```haskell
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 差不多。

```haskell
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

![](/files/-MSkgk668zzyWJlAJc2q)

学过数学的你对集合的 comprehension (Set Comprehension) 概念一定不会陌生。通过它，可以从既有的集合中按照规则产生一个新集合。前十个偶数的 set comprehension 可以表示为![](/files/ikQNdgeQiAlRWOIDrmM2)，竖线左端的部分是输出函数，`x` 是变量，`N` 是输入集合。在 Haskell 下，我们可以通过类似 `take 10 [2,4..]` 的代码来实现。但若是把简单的乘 2 改成更复杂的函数操作该怎么办呢？用 list comprehension，它与 set comprehension 十分的相似，用它取前十个偶数轻而易举。这个 list comprehension 可以表示为：

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

如你所见，结果正确。给这个 comprehension 再添个限制条件 (predicate)，它与前面的条件由一个逗号分隔。在这里，我们要求只取乘以 2 后大于等于 12 的元素。

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

cool，灵了。若是取 50 到 100 间所有除7的余数为 3 的元素该怎么办？简单：

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

成功！从一个 List 中筛选出符合特定限制条件的操作也可以称为过滤 (filtering)。即取一组数并且按照一定的限制条件过滤它们。再举个例子 吧，假如我们想要一个 comprehension，它能够使 List 中所有大于 10 的奇数变为 `"BANG"`，小于 10 的奇数变为 `"BOOM"`，其他则统统扔掉。方便重用起见，我们将这个 comprehension 置于一个函数之中。

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

这个 comprehension 的最后部分就是限制条件，使用 `odd` 函数判断是否为奇数：返回 `True`，就是奇数，该 List 中的元素才被包含。

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

也可以加多个限制条件。若要达到 10 到 20 间所有不等于 13，15 或 19 的数，可以这样：

```haskell
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]`， 要取它们所有组合的积，可以这样：

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

意料之中，得到的新 List 长度为 9。若只取乘积大于 50 的结果该如何？

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

取个包含一组名词和形容词的 List comprehension 吧，写诗的话也许用得着。

```haskell
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'`!

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

`_` 表示我们并不关心从 List 中取什么值，与其弄个永远不用的变量，不如直接一个 `_`。这个函数将一个 List 中所有元素置换为 1，并且使其相加求和。得到的结果便是我们的 List 长度。友情提示：字串也是 List，完全可以使用 list comprehension 来处理字串。如下是个除去字串中所有非大写字母的函数：

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

测试一下：

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

在这里，限制条件做了所有的工作。它说：只有在 `['A'..'Z']` 之间的字符才可以被包含。

若操作含有 List 的 List，使用嵌套的 List comprehension 也是可以的。假设有个包含许多数值的 List 的 List，让我们在不拆开它的前提下除去其中的所有奇数：

```haskell
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

![](/files/iQa30u33AT4ISv92XIzH)

从某种意义上讲，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)]`，就会报出以下的错误：

```haskell
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** 返回一个序对的首项。

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

**snd** 返回序对的尾项。

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

```
*Note*：这两个函数仅对序对有效，而不能应用于三元组，四元组和五元组之上。稍后，我们将过一遍从 Tuple 中取数据的所有方式。
```

有个函数很 cool，它就是 `zip`。它可以用来生成一组序对 (Pair) 的 List。它取两个 List，然后将它们交叉配对，形成一组序对的 List。它很简单，却很实用，尤其是你需要组合或是遍历两个 List 时。如下是个例子：

```haskell
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 会怎么样？

```haskell
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 也是可以的：

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

接下来考虑一个同时应用到 List 和 Tuple 的问题：如何取得所有三边长度皆为整数且小于等于 10，周长为 24 的直角三角形？首先，把所有三边长度小于等于 10 的三角形都列出来：

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

刚才我们是从三个 List 中取值，并且通过输出函数将其组合为一个三元组。只要在 ghci 下边调用 triangles，你就会得到所有三边都小于等于 10 的三角形。我们接下来给它添加一个限制条件，令其必须为直角三角形。同时也考虑上 `b` 边要短于斜边，`a` 边要短于 `b` 边情况：

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

已经差不多了。最后修改函数，告诉它只要周长为 24 的三角形。

```haskell
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)]
```

得到正确结果！这便是函数式编程语言的一般思路：先取一个初始的集合并将其变形，执行过滤条件，最终取得正确的结果。


# ch03


# Types and Typeclasses

## Type

![](/files/-MlVFmlP_HbIzunxsWY_)

之前我们有说过 Haskell 是 Static Type，这表示在编译时期每个表达式的型别都已经确定下来，这提高了代码的安全性。若代码中有让布尔值与数字相除的动作，就不会通过编译。这样的好处就是与其让进程在运行时崩溃，不如在编译时就找出可能的错误。Haskell 中所有东西都有型别，因此在编译的时候编译器可以做到很多事情。

与 Java 和 Pascal 不同，Haskell 支持型别推导。写下一个数字，你就没必要另告诉 Haskell 说"它是个数字"，它自己能推导出来。这样我们就不必在每个函数或表达式上都标明其型别了。在前面我们只简单涉及一下 Haskell 的型别方面的知识，但是理解这一型别系统对于 Haskell 的学习是至关重要的。

型别是每个表达式都有的某种标签，它标明了这一表达式所属的范畴。例如，表达式 `True` 是 `boolean` 型，`"hello"`是个字串，等等。

可以使用 ghci 来检测表达式的型别。使用 `:t` 命令后跟任何可用的表达式，即可得到该表达式的型别，先试一下：

```haskell
ghci> :t 'a'  
'a' :: Char  
ghci> :t True  
True :: Bool  
ghci> :t "HELLO!"  
"HELLO!" :: [Char]  
ghci> :t (True, 'a')  
(True, 'a') :: (Bool, Char)  
ghci> :t 4 == 5  
4 == 5 :: Bool
```

![](/files/-MlVFmlRoLyxB3CFfu7q)

可以看出，`:t` 命令处理一个表达式的输出结果为表达式后跟 `::` 及其型别，`::` 读作"它的型别为"。凡是明确的型别，其首字母必为大写。`'a'`, 如它的样子，是 `Char` 型别，易知是个字符 (character)。`True` 是 `Bool` 型别，也靠谱。不过这又是啥，检测 `"hello"` 得一个 `[Char]` 这方括号表示一个 List，所以我们可以将其读作"一组字符的 List"。而与 List 不同，每个 Tuple 都是独立的型别，于是 `(True,'a')` 的型别是 `(Bool,Char)`，而 `('a','b','c')` 的型别为 `(Char,Char,Char)`。`4==5` 一定回传 `False`，所以它的型别为 Bool。

同样，函数也有型别。编写函数时，给它一个明确的型别声明是个好习惯，比较短的函数就不用多此一举了。还记得前面那个过滤大写字母的 List Comprehension 吗？给它加上型别声明便是这个样子：

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

`removeNonUppercase` 的型别为 `[Char]->[Char]`，从它的参数和回传值的型别上可以看出，它将一个字串映射为另一个字串。`[Char]` 与 `String` 是等价的，但使用 `String` 会更清晰：`removeNonUppercase :: String -> String`。编译器会自动检测出它的型别，我们还是标明了它的型别声明。要是多个参数的函数该怎样？如下便是一个将三个整数相加的简单函数。

```haskell
addThree :: Int -> Int -> Int -> Int  
addThree x y z = x + y + z
```

参数之间由 `->` 分隔，而与回传值之间并无特殊差异。回传值是最后一项，参数就是前三项。稍后，我们将讲解为何只用 `->` 而不是 `Int,Int,Int->Int` 之类"更好看"的方式来分隔参数。

如果你打算给你编写的函数加上个型别声明却拿不准它的型别是啥，只要先不写型别声明，把函数体写出来，再使用 `:t` 命令测一下即可。函数也是表达式，所以 `:t` 对函数也是同样可用的。

如下是几个常见的型别：

**Int** 表示整数。7 可以是 Int，但 7.2 不可以。Int 是有界的，也就是说它由上限和下限。对 32 位的机器而言，上限一般是 `2147483647`，下限是 `-2147483648`。

**Integer** 表示...呃...也是整数，但它是无界的。这就意味着可以用它存放非常非常大的数，我是说非常大。它的效率不如 Int 高。

```haskell
factorial :: Integer -> Integer  
factorial n = product [1..n]
```

```haskell
ghci> factorial 50  
30414093201713378043612608166064768844377641568960512000000000000
```

**Float** 表示单精度的浮点数。

```haskell
circumference :: Float -> Float  
circumference r = 2 * pi * r
```

```haskell
ghci> circumference 4.0  
25.132742
```

**Double** 表示双精度的浮点数。

```haskell
circumference' :: Double -> Double  
circumference' r = 2 * pi * r
```

```haskell
ghci> circumference' 4.0  
25.132741228718345
```

**Bool** 表示布尔值，它只有两种值：`True` 和 `False`。

**Char** 表示一个字符。一个字符由单引号括起，一组字符的 List 即为字串。

Tuple 的型别取决于它的长度及其中项的型别。注意，空 Tuple 同样也是个型别，它只有一种值：`()`。

## Type variables

你觉得 `head` 函数的型别是啥？它可以取任意型别的 List 的首项，是怎么做到的呢？我们查一下！

```haskell
ghci> :t head  
head :: [a] -> a
```

![](/files/-MlVFmlVhYjCluhCZyah)

嗯! `a` 是啥？型别吗？想想我们在前面说过，凡是型别其首字母必大写，所以它不会是个型别。它是个型别变量，意味着 a 可以是任意的型别。这一点与其他语言中的泛型 (generic) 很相似，但在 Haskell 中要更为强大。它可以让我们轻而易举地写出型别无关的函数。使用到型别变量的函数被称作"多态函数 "，`head` 函数的型别声明里标明了它可以取任意型别的 List 并回传其中的第一个元素。

在命名上，型别变量使用多个字符是合法的，不过约定俗成，通常都是使用单个字符，如 `a`, `b` ,`c` ,`d`...

还记得 `fst`？我们查一下它的型别：

```haskell
ghci> :t fst  
fst :: (a, b) -> a
```

可以看到`fst`取一个包含两个型别的 Tuple 作参数，并以第一个项的型别作为回传值。这便是 `fst` 可以处理一个含有两种型别项的 pair 的原因。注意，`a` 和 `b` 是不同的型别变量，但它们不一定非得是不同的型别，它只是标明了首项的型别与回传值的型别相同。

## Typeclasses入门

![](/files/-MlVFmlW7iZih54Hz6Au)

型别定义行为的接口，如果一个型别属于某 Typeclass，那它必实现了该 Typeclass 所描述的行为。很多从 OOP 走过来的人们往往会把 Typeclass 当成面向对象语言中的 `class` 而感到疑惑，呃，它们不是一回事。易于理解起见，你可以把它看做是 Java 的 interface。

`==` 函数的型别声明是怎样的？

```haskell
ghci> :t (==)  
(==) :: (Eq a) => a -> a -> Bool
```

```
*Note*: 判断相等的==运算子是函数，``+-*/``之类的运算子也是同样。在缺省条件下，它们多为中缀函数。若要检查它的型别，就必须得用括号括起使之作为另一个函数，或者说以首码函数的形式调用它。
```

有意思。在这里我们见到个新东西：`=>` 符号。它左边的部分叫做型别约束。我们可以这样阅读这段型别声明："相等函数取两个相同型别的值作为参数并回传一个布尔值，而这两个参数的型别同在 Eq 类之中(即型别约束)"

**Eq** 这一 Typeclass 提供了判断相等性的接口，凡是可比较相等性的型别必属于 `Eq` class。

```haskell
ghci> 5 == 5   
True   
ghci> 5 /= 5   
False   
ghci> 'a' == 'a'   
True   
ghci> "Ho Ho" == "Ho Ho"   
True   
ghci> 3.432 == 3.432   
True
```

`elem` 函数的型别为: `(Eq a)=>a->[a]->Bool`。这是它在检测值是否存在于一个 List 时使用到了==的缘故。

几个基本的 Typeclass：

**Eq** 包含可判断相等性的型别。提供实现的函数是 `==` 和 `/=`。所以，只要一个函数有Eq类的型别限制，那么它就必定在定义中用到了 `==` 和 `/=`。刚才说了，除函数以外的所有型别都属于 `Eq`，所以它们都可以判断相等性。

**Ord** 包含可比较大小的型别。除了函数以外，我们目前所谈到的所有型别都属于 `Ord` 类。`Ord` 包中包含了`<, >, <=, >=` 之类用于比较大小的函数。`compare` 函数取两个 `Ord` 类中的相同型别的值作参数，回传比较的结果。这个结果是如下三种型别之一：`GT, LT, EQ`。

```haskell
ghci> :t (>)  
(>) :: (Ord a) => a -> a -> Bool
```

型别若要成为Ord的成员，必先加入Eq家族。

```haskell
ghci> "Abrakadabra" < "Zebra"  
True  
ghci> "Abrakadabra" `compare` "Zebra"  
LT  
ghci> 5 >= 2  
True  
ghci> 5 `compare` 3  
GT
```

**Show** 的成员为可用字串表示的型别。目前为止，除函数以外的所有型别都是 `Show` 的成员。操作 Show Typeclass，最常用的函数表示 `show`。它可以取任一Show的成员型别并将其转为字串。

```haskell
ghci> show 3  
"3"  
ghci> show 5.334  
"5.334"  
ghci> show True  
"True"
```

**Read** 是与 `Show` 相反的 Typeclass。`read` 函数可以将一个字串转为 `Read` 的某成员型别。

```haskell
ghci> read "True" || False  
True  
ghci> read "8.2" + 3.8  
12.0  
ghci> read "5" - 2  
3  
ghci> read "[1,2,3,4]" ++ [3]  
[1,2,3,4,3]
```

一切良好，如上的所有型别都属于这一 Typeclass。尝试 `read "4"` 又会怎样？

```haskell
ghci> read "4"  
< interactive >:1:0:  
    Ambiguous type variable `a' in the constraint:  
      `Read a' arising from a use of `read' at <interactive>:1:0-7  
    Probable fix: add a type signature that fixes these type variable(s)
```

ghci 跟我们说它搞不清楚我们想要的是什么样的回传值。注意调用 `read` 后跟的那部分，ghci 通过它来辨认其型别。若要一个 `boolean` 值，他就知道必须得回传一个 `Bool` 型别的值。但在这里它只知道我们要的型别属于 Read Typeclass，而不能明确到底是哪个。看一下 `read` 函数的型别声明吧：

```haskell
ghci> :t read  
read :: (Read a) => String -> a
```

看，它的回传值属于 ReadTypeclass，但我们若用不到这个值，它就永远都不会得知该表达式的型别。所以我们需要在一个表达式后跟`::` 的*型别注释*，以明确其型别。如下：

```haskell
ghci> read "5" :: Int  
5  
ghci> read "5" :: Float  
5.0  
ghci> (read "5" :: Float) * 4  
20.0  
ghci> read "[1,2,3,4]" :: [Int]  
[1,2,3,4]  
ghci> read "(3, 'a')" :: (Int, Char)  
(3, 'a')
```

编译器可以辨认出大部分表达式的型别，但遇到 `read "5"` 的时候它就搞不清楚究竟该是 Int 还是 Float 了。只有经过运算，Haskell 才会明确其型别；同时由于 Haskell 是静态的，它还必须得在 编译前搞清楚所有值的型别。所以我们就最好提前给它打声招呼："嘿，这个表达式应该是这个型别，省得你认不出来！"

**Enum** 的成员都是连续的型别 -- 也就是可枚举。`Enum` 类存在的主要好处就在于我们可以在 `Range` 中用到它的成员型别：每个值都有后继子 (successer) 和前置子 (predecesor)，分别可以通过 `succ` 函数和 `pred` 函数得到。该 Typeclass 包含的型别有：`()`, `Bool`, `Char`, `Ordering`, `Int`, `Integer`, `Float` 和 `Double`。

```haskell
ghci> ['a'..'e']  
"abcde"  
ghci> [LT .. GT]  
[LT,EQ,GT]  
ghci> [3 .. 5]  
[3,4,5]  
ghci> succ 'B'  
'C'
```

**Bounded** 的成员都有一个上限和下限。

```haskell
ghci> minBound :: Int  
-2147483648  
ghci> maxBound :: Char  
'\1114111'  
ghci> maxBound :: Bool  
True  
ghci> minBound :: Bool  
False
```

`minBound` 和 `maxBound` 函数很有趣，它们的型别都是 `(Bounded a) => a`。可以说，它们都是多态常量。

如果其中的项都属于 `Bounded` Typeclass，那么该 Tuple 也属于 `Bounded`

```haskell
ghci> maxBound :: (Bool, Int, Char)  
(True,2147483647,'\1114111')
```

**Num** 是表示数字的 Typeclass，它的成员型别都具有数字的特征。检查一个数字的型别：

```haskell
ghci> :t 20  
20 :: (Num t) => t
```

看样子所有的数字都是多态常量，它可以作为所有 `Num` Typeclass中的成员型别。以上便是 `Num` Typeclass 中包含的所有型别，检测 `*` 运算子的型别，可以发现它可以处理一切的数字：

```haskell
ghci> :t (*)  
(*) :: (Num a) => a -> a -> a
```

它只取两个相同型别的参数。所以 `(5 :: Int) * (6 :: Integer)` 会引发一个型别错误，而 `5 * (6 :: Integer)` 就不会有问题。

型别只有亲近 `Show` 和 `Eq`，才可以加入 `Num`。

**Integral** 同样是表示数字的 Typeclass。`Num` 包含所有的数字：实数和整数。而 Integral 仅包含整数，其中的成员型别有 `Int` 和 `Integer`。

**Floating** 仅包含浮点型别：`Float` 和 `Double`。

有个函数在处理数字时会非常有用，它便是 **fromIntegral**。其型别声明为： `fromIntegral :: (Num b, Integral a) => a -> b`。从中可以看出，它取一个整数做参数并回传一个更加通用的数字，这在同时处理整数和浮点时会尤为有用。举例来说，`length` 函数的型别声明为：`length :: [a] -> Int`，而非更通用的形式，如 `length :: (Num b) => [a] -> b`。这应该是历史原因吧，反正我觉得挺蠢。如果取了一个 List 长度的值再给它加 3.2 就会报错，因为这是将浮点数和整数相加。面对这种情况，我们就用 `fromIntegral (length [1,2,3,4]) + 3.2` 来解决。

注意到，`fromIntegral` 的型别声明中用到了多个型别约束。如你所见，只要将多个型别约束放到括号里用逗号隔开即可。


# ch04


# 函数的语法

## 模式匹配 (Pattern matching)

![](/files/-LjUmuKEwFZY24jYJj09)

本章讲的就是 Haskell 那套独特的语法结构，先从模式匹配开始。模式匹配通过检查数据的特定结构来检查其是否匹配，并按模式从中取得数据。

在定义函数时，你可以为不同的模式分别定义函数本身，这就让代码更加简洁易读。你可以匹配一切数据型别 --- 数字，字符，List，元组，等等。我们弄个简单函数，让它检查我们传给它的数字是不是 7。

```haskell
lucky :: (Integral a) => a -> String  
lucky 7 = "LUCKY NUMBER SEVEN!"  
lucky x = "Sorry, you're out of luck, pal!"
```

在调用 `lucky` 时，模式会从上至下进行检查，一旦有匹配，那对应的函数体就被应用了。这个模式中的唯一匹配是参数为 7，如果不是 7，就转到下一个模式，它匹配一切数值并将其绑定为 `x` 。这个函数完全可以使用 `if` 实现，不过我们若要个分辨 1 到 5 中的数字，而无视其它数的函数该怎么办？要是没有模式匹配的话，那可得好大一棵 `if-else` 树了！

```haskell
sayMe :: (Integral a) => a -> String  
sayMe 1 = "One!"  
sayMe 2 = "Two!"  
sayMe 3 = "Three!"  
sayMe 4 = "Four!"  
sayMe 5 = "Five!"  
sayMe x = "Not between 1 and 5"
```

注意下，如果我们把最后匹配一切的那个模式挪到最前，它的结果就全都是 `"Not between 1 and 5"` 了。因为它自己匹配了一切数字，不给后面的模式留机会。

记得前面实现的那个阶乘函数么？当时是把 `n` 的阶乘定义成了 `product [1..n]`。也可以写出像数学那样的递归实现，先说明 0 的阶乘是 1 ，再说明每个正整数的阶乘都是这个数与它前驱 (predecessor) 对应的阶乘的积。如下便是翻译到 Haskell 的样子：

```haskell
factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1)
```

这就是我们定义的第一个递归函数。递归在 Haskell 中十分重要，我们会在后面深入理解。如果拿一个数(如 3)调用 `factorial` 函数，这就是接下来的计算步骤：先计算 `3*factorial 2`，`factorial 2` 等于 `2*factorial 1`，也就是 `3*(2*(factorial 1))`。`factorial 1` 等于 `1*factorial 0`，好，得 `3*(2*(1*factorial 0))`，递归在这里到头了，嗯 --- 我们在万能匹配前面有定义，0 的阶乘是 1。于是最终的结果等于 `3*(2*(1*1))`。若是把第二个模式放在前面，它就会捕获包括 0 在内的一切数字，这一来我们的计算就永远都不会停止了。这便是为什么说模式的顺序是如此重要：它总是优先匹配最符合的那个，最后才是那个万能的。

模式匹配也会失败。假如这个函数：

```haskell
charName :: Char -> String  
charName 'a' = "Albert"  
charName 'b' = "Broseph"  
charName 'c' = "Cecil"
```

拿个它没有考虑到的字符去调用它，你就会看到这个：

```haskell
ghci> charName 'a'  
"Albert"  
ghci> charName 'b'  
"Broseph"  
ghci> charName 'h'  
"*** Exception: tut.hs:(53,0)-(55,21): Non-exhaustive patterns in function charName
```

它告诉我们说，这个模式不够全面。因此，在定义模式时，一定要留一个万能匹配的模式，这样我们的进程就不会为了不可预料的输入而崩溃了。

对 Tuple 同样可以使用模式匹配。写个函数，将二维空间中的矢量相加该如何？将它们的 `x` 项和 `y` 项分别相加就是了。如果不了解模式匹配，我们很可能会写出这样的代码：

```haskell
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors a b = (fst a + fst b, snd a + snd b)
```

嗯，可以运行。但有更好的方法，上模式匹配：

```haskell
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
```

there we go！好多了！注意，它已经是个万能的匹配了。两个 `addVector` 的型别都是 `addVectors:: (Num a) => (a,a) -> (a,a) -> (a,a)`，我们就能够保证，两个参数都是序对 (Pair) 了。

`fst` 和 `snd` 可以从序对中取出元素。三元组 (Tripple) 呢？嗯，没现成的函数，得自己动手：

```haskell
first :: (a, b, c) -> a  
first (x, _, _) = x  

second :: (a, b, c) -> b  
second (_, y, _) = y  

third :: (a, b, c) -> c  
third (_, _, z) = z
```

这里的 `_` 就和 List Comprehension 中一样。表示我们不关心这部分的具体内容。

说到 List Comprehension，我想起来在 List Comprehension 中也能用模式匹配：

```haskell
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]  
ghci> [a+b | (a,b) <- xs]  
[4,7,6,8,11,4]
```

一旦模式匹配失败，它就简单挪到下个元素。

对 List 本身也可以使用模式匹配。你可以用 `[]` 或 `:` 来匹配它。因为 `[1,2,3]` 本质就是 `1:2:3:[]` 的语法糖。你也可以使用前一种形式，像 `x:xs` 这样的模式可以将 List 的头部绑定为 `x`，尾部绑定为 `xs`。如果这 List 只有一个元素，那么 `xs` 就是一个空 List。

```
*Note*：``x:xs`` 这模式的应用非常广泛，尤其是递归函数。不过它只能匹配长度大于等于 1 的 List。
```

如果你要把 List 的前三个元素都绑定到变量中，可以使用类似 `x:y:z:xs` 这样的形式。它只能匹配长度大于等于 3 的 List。

我们已经知道了对 List 做模式匹配的方法，就实现个我们自己的 `head` 函数。

```haskell
head' :: [a] -> a  
head' [] = error "Can't call head on an empty list, dummy!"  
head' (x:_) = x
```

看看管不管用：

```haskell
ghci> head' [4,5,6]  
4  
ghci> head' "Hello"  
'H'
```

漂亮！注意下，你若要绑定多个变量(用 `_` 也是如此)，我们必须用括号将其括起。同时注意下我们用的这个 `error` 函数，它可以生成一个运行时错误，用参数中的字串表示对错误的描述。它会直接导致进程崩溃，因此应谨慎使用。可是对一个空 List 取 `head` 真的不靠谱哇。

弄个简单函数，让它用非标准的英语给我们展示 List 的前几项。

```haskell
tell :: (Show a) => [a] -> String  
tell [] = "The list is empty"  
tell (x:[]) = "The list has one element: " ++ show x  
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y  
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
```

这个函数顾及了空 List，单元素 List，双元素 List 以及较长的 List，所以这个函数很安全。`(x:[])` 与 `(x:y:[])` 也可以写作 `[x]` 和 `[x,y]` (有了语法糖，我们不必多加括号)。不过 `(x:y:_)` 这样的模式就不行了，因为它匹配的 List 长度不固定。

我们曾用 List Comprehension 实现过自己的 `length` 函数，现在用模式匹配和递归重新实现它：

```haskell
length' :: (Num b) => [a] -> b  
length' [] = 0  
length' (_:xs) = 1 + length' xs
```

这与先前写的那个 `factorial` 函数很相似。先定义好未知输入的结果 --- 空 List，这也叫作边界条件。再在第二个模式中将这 List 分割为头部和尾部。说，List 的长度就是其尾部的长度加 1。匹配头部用的 `_`，因为我们并不关心它的值。同时也应明确，我们顾及了 List 所有可能的模式：第一个模式匹配空 List，第二个匹配任意的非空 List。

看下拿 `"ham"` 调用 `length'` 会怎样。首先它会检查它是否为空 List。显然不是，于是进入下一模式。它匹配了第二个模式，把它分割为头部和尾部并无视掉头部的值，得长度就是 `1+length' "am"`。ok。以此类推，`"am"` 的 `length` 就是 `1+length' "m"`。好，现在我们有了 `1+(1+length' "m")`。`length' "m"` 即 `1+length ""` (也就是 `1+length' []` )。根据定义，`length' []` 等于 `0`。最后得 `1+(1+(1+0))`。

再实现 `sum`。我们知道空 List 的和是 0，就把它定义为一个模式。我们也知道一个 List 的和就是头部加上尾部的和的和。写下来就成了：

```haskell
sum' :: (Num a) => [a] -> a  
sum' [] = 0  
sum' (x:xs) = x + sum' xs
```

还有个东西叫做 `as` 模式，就是将一个名字和 `@` 置于模式前，可以在按模式分割什么东西时仍保留对其整体的引用。如这个模式 `xs@(x:y:ys)`，它会匹配出与 `x:y:ys` 对应的东西，同时你也可以方便地通过 `xs` 得到整个 List，而不必在函数体中重复 `x:y:ys`。看下这个 quick and dirty 的例子：

```haskell
capital :: String -> String  
capital "" = "Empty string, whoops!"  
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
```

```haskell
ghci> capital "Dracula"  
"The first letter of Dracula is D"
```

我们使用 `as` 模式通常就是为了在较大的模式中保留对整体的引用，从而减少重复性的工作。

还有——你不可以在模式匹配中使用 `++`。若有个模式是 `(xs++ys)`，那么这个 List 该从什么地方分开呢？不靠谱吧。而 `(xs++[x,y,z])` 或只一个 `(xs++[x])` 或许还能说的过去，不过出于 List 的本质，这样写也是不可以的。

## 什么是 Guards

模式用来检查一个值是否合适并从中取值，而 guard 则用来检查一个值的某项属性是否为真。咋一听有点像是 `if` 语句，实际上也正是如此。不过处理多个条件分支时 guard 的可读性要高些，并且与模式匹配契合的很好。

![](/files/-LjUmuKHZR9w2PC2Qesk)

在讲解它的语法前，我们先看一个用到 guard 的函数。它会依据你的 BMI 值 (body mass index，身体质量指数)来不同程度地侮辱你。BMI 值即为体重除以身高的平方。如果小于 18.5，就是太瘦；如果在 18.5 到 25 之间，就是正常；25 到 30 之间，超重；如果超过 30，肥胖。这就是那个函数(我们目前暂不为您计算 BMI，它只是直接取一个 BMI 值)。

```haskell
bmiTell :: (RealFloat a) => a -> String  
bmiTell bmi  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"
```

guard 由跟在函数名及参数后面的竖线标志，通常他们都是靠右一个缩进排成一列。一个 guard 就是一个布尔表达式，如果为真，就使用其对应的函数体。如果为假，就送去见下一个 guard，如之继续。如果我们用 24.3 调用这个函数，它就会先检查它是否小于等于 18.5，显然不是，于是见下一个 guard。24.3 小于 25.0，因此通过了第二个 guard 的检查，就返回第二个字串。

在这里则是相当的简洁，不过不难想象这在命令式语言中又会是怎样的一棵 if-else 树。由于 if-else 的大树比较杂乱，若是出现问题会很难发现，guard 对此则十分清楚。

最后的那个 guard 往往都是 `otherwise`，它的定义就是简单一个 `otherwise = True` ，捕获一切。这与模式很相像，只是模式检查的是匹配，而它们检查的是布尔表达式 。如果一个函数的所有 guard 都没有通过(而且没有提供 `otherwise` 作万能匹配)，就转入下一模式。这便是 guard 与模式契合的地方。如果始终没有找到合适的 guard 或模式，就会发生一个错误。

当然，guard 可以在含有任意数量参数的函数中使用。省得用户在使用这函数之前每次都自己计算 `bmi`。我们修改下这个函数，让它取身高体重为我们计算。

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                 = "You're a whale, congratulations!"
```

你可以测试自己胖不胖。

```haskell
ghci> bmiTell 85 1.90  
"You're supposedly normal. Pffft, I bet you're ugly!"
```

运行的结果是我不太胖。不过程序却说我很丑。

要注意一点，函数的名字和参数的后面并没有 `=`。许多初学者会造成语法错误，就是因为在后面加上了 `=`。

另一个简单的例子：写个自己的 `max` 函数。应该还记得，它是取两个可比较的值，返回较大的那个。

```haskell
max' :: (Ord a) => a -> a -> a  
max' a b   
    | a > b     = a  
    | otherwise = b
```

guard 也可以塞在一行里面。但这样会丧失可读性，因此是不被鼓励的。即使是较短的函数也是如此，不过出于展示，我们可以这样重写 `max'`：

```haskell
max' :: (Ord a) => a -> a -> a  
max' a b | a > b = a | otherwise = b
```

这样的写法根本一点都不容易读。

我们再来试试用 guard 实现我们自己的 `compare` 函数：

```haskell
myCompare :: (Ord a) => a -> a -> Ordering  
a `myCompare` b  
    | a > b     = GT  
    | a == b    = EQ  
    | otherwise = LT
```

```haskell
ghci> 3 `myCompare` 2  
GT
```

```
*Note*：通过反单引号，我们不仅可以以中缀形式调用函数，也可以在定义函数的时候使用它。有时这样会更易读。
```

## 关键字 Where

前一节中我们写了这个 `bmi` 计算函数：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                   = "You're a whale, congratulations!"
```

注意，我们重复了 3 次。我们重复了 3 次。程序员的字典里不应该有"重复"这个词。既然发现有重复，那么给它一个名字来代替这三个表达式会更好些。嗯，我们可以这样修改：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2
```

我们的 `where` 关键字跟在 guard 后面(最好是与竖线缩进一致)，可以定义多个名字和函数。这些名字对每个 guard 都是可见的，这一来就避免了重复。如果我们打算换种方式计算 `bmi`，只需进行一次修改就行了。通过命名，我们提升了代码的可读性，并且由于 `bmi` 只计算了一次，函数的执行效率也有所提升。我们可以再做下修改：

```haskell
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= skinny = "You're underweight, you emo, you!"  
    | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
    | otherwise     = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2  
          skinny = 18.5  
          normal = 25.0  
          fat = 30.0
```

函数在 `where` 绑定中定义的名字只对本函数可见，因此我们不必担心它会污染其他函数的命名空间。注意，其中的名字都是一列垂直排开，如果不这样规范，Haskell 就搞不清楚它们在哪个地方了。

`where` 绑定不会在多个模式中共享。如果你在一个函数的多个模式中重复用到同一名字，就应该把它置于全局定义之中。

`where` 绑定也可以使用*模式匹配*！前面那段代码可以改成：

```haskell
...  
where bmi = weight / height ^ 2  
      (skinny, normal, fat) = (18.5, 25.0, 30.0)
```

我们再搞个简单函数，让它告诉我们姓名的首字母：

```haskell
initials :: String -> String -> String  
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."  
    where (f:_) = firstname  
          (l:_) = lastname
```

我们完全按可以在函数的参数上直接使用模式匹配(这样更短更简洁)，在这里只是为了演示在 `where` 语句中同样可以使用模式匹配：

`where` 绑定可以定义名字，也可以定义函数。保持健康的编程语言风格，我们搞个计算一组 `bmi` 的函数：

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi w h | (w, h) <- xs] 
    where bmi weight height = weight / height ^ 2
```

这就全了！在这里将 `bmi` 搞成一个函数，是因为我们不能依据参数直接进行计算，而必须先从传入函数的 List 中取出每个序对并计算对应的值。

`where` 绑定还可以一层套一层地来使用。 有个常见的写法是，在定义一个函数的时候也写几个辅助函数摆在 `where` 绑定中。 而每个辅助函数也可以透过 `where` 拥有各自的辅助函数。

## 关键字 Let

`let` 绑定与 `where` 绑定很相似。`where` 绑定是在函数底部定义名字，对包括所有 guard 在内的整个函数可见。`let` 绑定则是个表达式，允许你在任何位置定义局部变量，而对不同的 guard 不可见。正如 Haskell 中所有赋值结构一样，`let` 绑定也可以使用模式匹配。看下它的实际应用！这是个依据半径和高度求圆柱体表面积的函数：

```haskell
cylinder :: (RealFloat a) => a -> a -> a  
cylinder r h = 
    let sideArea = 2 * pi * r * h  
        topArea = pi * r ^2  
    in  sideArea + 2 * topArea
```

![](/files/-LjUmuKJ5K-EDHDrqQlt)

`let` 的格式为 `let [bindings] in [expressions]`。在 `let` 中绑定的名字仅对 `in` 部分可见。`let` 里面定义的名字也得对齐到一列。不难看出，这用 `where` 绑定也可以做到。那么它俩有什么区别呢？看起来无非就是，`let` 把绑定放在语句前面而 `where` 放在后面嘛。

不同之处在于，`let` 绑定本身是个表达式，而 `where` 绑定则是个语法结构。还记得前面我们讲if语句时提到它是个表达式，因而可以随处安放？

```haskell
ghci> [if 5 > 3 then "Woo" else "Boo", if 'a' > 'b' then "Foo" else "Bar"]  
["Woo", "Bar"]  
ghci> 4 * (if 10 > 5 then 10 else 0) + 2  
42
```

用 `let` 绑定也可以实现：

```haskell
ghci> 4 * (let a = 9 in a + 1) + 2  
42
```

`let` 也可以定义局部函数：

```haskell
ghci> [let square x = x * x in (square 5, square 3, square 2)]  
[(25,9,4)]
```

若要在一行中绑定多个名字，再将它们排成一列显然是不可以的。不过可以用分号将其分开。

```haskell
ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)  
(6000000,"Hey there!")
```

最后那个绑定后面的分号不是必须的，不过加上也没关系。如我们前面所说，你可以在 `let` 绑定中使用模式匹配。这在从 Tuple 取值之类的操作中很方便。

```haskell
ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100  
600
```

你也可以把 `let` 绑定放到 List Comprehension 中。我们重写下那个计算 `bmi` 值的函数，用个 `let` 替换掉原先的 `where`。

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
```

List Comprehension 中 `let` 绑定的样子和限制条件差不多，只不过它做的不是过滤，而是绑定名字。`let` 中绑定的名字在输出函数及限制条件中都可见。这一来我们就可以让我们的函数只返回胖子的 `bmi` 值：

```haskell
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
```

在 `(w, h) <- xs` 这里无法使用 `bmi` 这名字，因为它在 `let` 绑定的前面。

在 List Comprehension 中我们忽略了 `let` 绑定的 `in` 部分，因为名字的可见性已经预先定义好了。不过，把一个 `let...in` 放到限制条件中也是可以的，这样名字只对这个限制条件可见。在 ghci 中 `in` 部分也可以省略，名字的定义就在整个交互中可见。

```haskell
ghci> let zoot x y z = x * y + z  
ghci> zoot 3 9 2  
29  
ghci> let boot x y z = x * y + z in boot 3 4 2  
14  
ghci> boot  
< interactive>:1:0: Not in scope: `boot'
```

你说既然 `let` 已经这么好了，还要 `where` 干嘛呢？嗯，`let` 是个表达式，定义域限制的相当小，因此不能在多个 guard 中使用。一些朋友更喜欢 `where`，因为它是跟在函数体后面，把主函数体距离型别声明近一些会更易读。

## Case expressions

![](/files/-LjUmuKMXDTmIrk3I1k1)

有命令式编程语言 (C, C++, Java, etc.) 的经验的同学一定会有所了解，很多命令式语言都提供了 `case` 语句。就是取一个变量，按照对变量的判断选择对应的代码块。其中可能会存在一个万能匹配以处理未预料的情况。

Haskell 取了这一概念融合其中。如其名，`case` 表达式就是，嗯，一种表达式。跟 `if..else` 和 `let` 一样的表达式。用它可以对变量的不同情况分别求值，还可以使用模式匹配。Hmm，取一个变量，对它模式匹配，执行对应的代码块。好像在哪儿听过？啊，就是函数定义时参数的模式匹配！好吧，模式匹配本质上不过就是 `case` 语句的语法糖而已。这两段代码就是完全等价的：

```haskell
head' :: [a] -> a  
head' [] = error "No head for empty lists!"  
head' (x:_) = x
```

```haskell
head' :: [a] -> a  
head' xs = case xs of [] -> error "No head for empty lists!"  
                      (x:_) -> x
```

看得出，*case*表达式的语法十分简单：

```haskell
case expression of pattern -> result  
                   pattern -> result  
                   pattern -> result  
                   ...
```

expression 匹配合适的模式。 一如预期地，第一个模式若匹配，就执行第一个区块的代码；否则就接下去比对下一个模式。如果到最后依然没有匹配的模式，就会产生运行时错误。

函数参数的模式匹配只能在定义函数时使用，而 `case` 表达式可以用在任何地方。例如：

```haskell
describeList :: [a] -> String  
describeList xs = "The list is " ++ case xs of [] -> "empty."  
                                               [x] -> "a singleton list."   
                                               xs -> "a longer list."
```

这在表达式中作模式匹配很方便，由于模式匹配本质上就是 `case` 表达式的语法糖，那么写成这样也是等价的：

```haskell
describeList :: [a] -> String  
describeList xs = "The list is " ++ what xs  
    where what [] = "empty."  
          what [x] = "a singleton list."  
          what xs = "a longer list."
```


# ch05


# 递归

## 你好，递归！

![](/files/-MlVFe2e-yY8c78EuArj)

前面的章节中我们简要谈了一下递归。而在本章，我们会深入地了解到它为何在 Haskell 中是如此重要，能够以递归思想写出简洁优雅的代码。

如果你还不知道什么是递归，就读这个句子。哈哈！开个玩笑而已！递归实际上是定义函数以调用自身的方式。在数学定义中，递归随处可见，如斐波那契数列 （fibonacci）。它先是定义两个非递归的数：`F(0)=0,F(1)=1`，表示斐波那契数列的前两个数为 0 和 1。然后就是对其他自然数，其斐波那契数就是它前面两个数字的和，即 `F(N)=F(N-1)+F(N-2)`。这样一来，`F(3)` 就是 `F(2)+F(1)`，进一步便是 `(F(1)+F(0))+F(1)`。已经下探到了前面定义的非递归斐波那契数，可以放心地说 `F(3)` 就是 2 了。在递归定义中声明的一两个非递归的值（如 `F(0)` 和 `F(1)`） 也可以称作*边界条件*，这对递归函数的正确求值至关重要。要是前面没有定义 `F(0)` 和 `F(1)` 的话，它下探到 0 之后就会进一步到负数，你就永远都得不到结果了。一不留神它就算到了 `F(-2000)=F(-2001)+F(-2002)`，并且永远都算不到头！

递归在 Haskell 中非常重要。命令式语言要求你提供求解的步骤，Haskell 则倾向于让你提供问题的描述。这便是 Haskell 没有 `while` 或 `for` 循环的原因，递归是我们的替代方案。

## 实作 Maximum

`maximum` 函数取一组可排序的 List（属于 Ord Typeclass） 做参数，并回传其中的最大值。想想，在命令式风格中这一函数该怎么实现。很可能你会设一个变量来存储当前的最大值，然后用循环遍历该 List，若存在比这个值更大的元素，则修改变量为这一元素的值。到最后，变量的值就是运算结果。唔！描述如此简单的算法还颇费了点口舌呢！

现在看看递归的思路是如何：我们先定下一个边界条件，即处理单个元素的 List 时，回传该元素。如果该 List 的头部大于尾部的最大值，我们就可以假定较长的 List 的最大值就是它的头部。而尾部若存在比它更大的元素，它就是尾部的最大值。就这么简单！现在，我们在 Haskell 中实现它

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' [] = error "maximum of empty list"  
maximum' [x] = x  
maximum' (x:xs)   
    | x > maxTail = x  
    | otherwise = maxTail  
    where maxTail = maximum' xs
```

如你所见，模式匹配与递归简直就是天造地设！大多数命令式语言中都没有模式匹配，于是你就得造一堆 if-else 来测试边界条件。而在这里，我们仅需要使用模式将其表示出来。第一个模式说，如果该 List 为空，崩溃！就该这样，一个空 List 的最大值能是啥？我不知道。第二个模式也表示一个边缘条件，它说， 如果这个 List 仅包含单个元素，就回传该元素的值。

现在是第三个模式，执行动作的地方。 通过模式匹配，可以取得一个 List 的头部和尾部。这在使用递归处理 List 时是十分常见的。出于习惯，我们用个 `where` 语句来表示 `maxTail` 作为该 List 中尾部的最大值，然后检查头部是否大于尾部的最大值。若是，回传头部；若非，回传尾部的最大值。

我们取个 List `[2,5,1]` 做例子来看看它的工作原理。当调用 `maximum'` 处理它时，前两个模式不会被匹配，而第三个模式匹配了它并将其分为 `2` 与 `[5,1]`。 `where` 子句再取 `[5,1]` 的最大值。于是再次与第三个模式匹配，并将 `[5,1]` 分割为 `5` 和 `[1]`。继续，`where` 子句取 `[1]` 的最大值，这时终于到了边缘条件！回传 `1`。进一步，将 `5` 与 `[1]` 中的最大值做比较，易得 `5`，现在我们就得到了 `[5,1]` 的最大值。再进一步，将 `2` 与 `[5,1]` 中的最大值相比较，可得 `5` 更大，最终得 `5`。

改用 `max` 函数会使代码更加清晰。如果你还记得，`max` 函数取两个值做参数并回传其中较大的值。如下便是用 `max` 函数重写的 `maximun'`

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' [] = error "maximum of empty list"  
maximum' [x] = x  
maximum' (x:xs) = max x (maximum' xs)
```

太漂亮了！一个 List 的最大值就是它的首个元素与它尾部中最大值相比较所得的结果，简明扼要。

![](/files/-MlVFe2iZ5LTg8hw9vjI)

## 来看几个递归函数

现在我们已经了解了递归的思路,接下来就使用递归来实现几个函数. 先实现下 `replicate` 函数, 它取一个 `Int` 值和一个元素做参数, 回传一个包含多个重复元素的 List, 如 `replicate 3 5` 回传 `[5,5,5]`. 考虑一下, 我觉得它的边界条件应该是负数. 如果要 `replicate` 重复某元素零次, 那就是空 List. 负数也是同样, 不靠谱.

```haskell
replicate' :: (Num i, Ord i) => i -> a -> [a]  
replicate' n x  
    | n <= 0    = []  
    | otherwise = x:replicate' (n-1) x
```

在这里我们使用了 guard 而非模式匹配, 是因为这里做的是布林判断. 如果 `n` 小于 0 就回传一个空 List, 否则, 回传以 `x` 作首个元素并后接重复 `n-1` 次 `x` 的 List. 最后, `(n-1)` 的那部分就会令函数抵达边缘条件.

```
*Note*: Num 不是 Ord 的子集, 表示数字不一定得拘泥于排序, 这就是在做加减法比较时要将 Num 与 Ord 型别约束区别开来的原因.
```

接下来实现 `take` 函数, 它可以从一个 List 取出一定数量的元素. 如 `take 3 [5,4,3,2,1]`, 得 `[5,4,3]`. 若要取零或负数个的话就会得到一个空 List. 同样, 若是从一个空 List中取值, 它会得到一个空 List. 注意, 这儿有两个边界条件, 写出来:

```haskell
take' :: (Num i, Ord i) => i -> [a] -> [a]  
take' n _  
    | n <= 0   = []  
take' _ []     = []  
take' n (x:xs) = x : take' (n-1) xs
```

![](/files/-MlVFe2jAx_YKPgxdZnK)

首个模式辨认若为 0 或负数, 回传空 List. 同时注意这里用了一个 guard 却没有指定 `otherwise` 部分, 这就表示 `n` 若大于 0, 会转入下一模式. 第二个模式指明了若试图从一个空 List 中取值, 则回传空 List. 第三个模式将 List 分割为头部和尾部, 然后表明从一个 List 中取多个元素等同于令 `x` 作头部后接从尾部取 `n-1` 个元素所得的 List. 假如我们要从 `[4,3,2,1]` 中取 3 个元素, 试着从纸上写出它的推导过程

`reverse` 函数简单地反转一个 List, 动脑筋想一下它的边界条件! 该怎样呢? 想想...是空 List! 空 List 的反转结果还是它自己. Okay, 接下来该怎么办? 好的, 你猜的出来. 若将一个 List 分割为头部与尾部, 那它反转的结果就是反转后的尾部与头部相连所得的 List.

```haskell
reverse' :: [a] -> [a]  
reverse' [] = []  
reverse' (x:xs) = reverse' xs ++ [x]
```

继续下去!

Haskell 支持无限 List，所以我们的递归就不必添加边界条件。这样一来，它可以对某值计算个没完, 也可以产生一个无限的数据结构，如无限 List。而无限 List 的好处就在于我们可以在任意位置将它断开.

`repeat` 函数取一个元素作参数, 回传一个仅包含该元素的无限 List. 它的递归实现简单的很, 看:

```haskell
repeat' :: a -> [a]  
repeat' x = x:repeat' x
```

调用 `repeat 3` 会得到一个以 3 为头部并无限数量的 3 为尾部的 List, 可以说 `repeat 3` 运行起来就是 `3:repeat 3` , 然后 `3:3:3:3` 等等. 若执行 `repeat 3`, 那它的运算永远都不会停止。而 `take 5 (repeat 3)` 就可以得到 5 个 3, 与 `replicate 5 3` 差不多.

`zip` 取两个 List 作参数并将其捆在一起。`zip [1,2,3] [2,3]` 回传 `[(1,2),(2,3)]`, 它会把较长的 List 从中间断开, 以匹配较短的 List. 用 `zip` 处理一个 List 与空 List 又会怎样? 嗯, 会得一个空 List, 这便是我们的限制条件, 由于 `zip` 取两个参数, 所以要有两个边缘条件

```haskell
zip' :: [a] -> [b] -> [(a,b)]  
zip' _ [] = []  
zip' [] _ = []  
zip' (x:xs) (y:ys) = (x,y):zip' xs ys
```

前两个模式表示两个 List 中若存在空 List, 则回传空 List. 第三个模式表示将两个 List 捆绑的行为, 即将其头部配对并后跟捆绑的尾部. 用 `zip` 处理 `[1,2,3]` 与 `['a','b']` 的话, 就会在 `[3]` 与 `[]` 时触及边界条件, 得到 `(1,'a'):(2,'b'):[]` 的结果,与 `[(1,'a'),(2,'b')]` 等价.

再实现一个标准库函数 -- `elem`! 它取一个元素与一个 List 作参数, 并检测该元素是否包含于此 List. 而边缘条件就与大多数情况相同, 空 List. 大家都知道空 List 中不包含任何元素, 便不必再做任何判断

```haskell
elem' :: (Eq a) => a -> [a] -> Bool  
elem' a [] = False  
elem' a (x:xs)  
    | a == x    = True  
    | otherwise = a `elem'` xs
```

这很简单明了。若头部不是该元素, 就检测尾部, 若为空 List 就回传 `False`.

## "快速"排序

![](/files/-MlVFe2lqOHhT5qjxD9n)

假定我们有一个可排序的 List, 其中元素的型别为 Ord Typeclass 的成员. 现在我们要给它排序! 有个排序算法非常的酷, 就是快速排序 (quick sort), 睿智的排序方法. 尽管它在命令式语言中也不过 10 行, 但在 Haskell 下边要更短, 更漂亮, 俨然已经成了 Haskell 的招牌了. 嗯, 我们在这里也实现一下. 或许会显得很俗气, 因为每个人都用它来展示 Haskell 究竟有多优雅!

它的型别声明应为 `quicksort :: (Ord a) => [a] -> [a]`, 没啥奇怪的. 边界条件呢? 如料，空 List。排过序的空 List 还是空 List。接下来便是算法的定义：*排过序的 List 就是令所有小于等于头部的元素在先(它们已经排过了序), 后跟大于头部的元素(它们同样已经拍过了序)*。 注意定义中有两次排序，所以就得递归两次！同时也需要注意算法定义的动词为"是"什么而非"做"这个, "做"那个, 再"做"那个...这便是函数式编程之美！如何才能从 List 中取得比头部小的那些元素呢？List Comprehension。好，动手写出这个函数！

```haskell
quicksort :: (Ord a) => [a] -> [a]  
quicksort [] = []  
quicksort (x:xs) =  
  let smallerSorted = quicksort [a | a <- xs, a <= x] 
      biggerSorted = quicksort [a | a <- xs, a > x]  
  in smallerSorted ++ [x] ++ biggerSorted
```

小小的测试一下, 看看结果是否正确\~

```haskell
ghci> quicksort [10,2,5,3,1,6,7,4,2,3,4,8,9]  
[1,2,2,3,3,4,4,5,6,7,8,9,10]  
ghci> quicksort "the quick brown fox jumps over the lazy dog"  
" abcdeeefghhijklmnoooopqrrsttuuvwxyz"
```

booyah! 如我所说的一样! 若给 `[5,1,9,4,6,7,3]` 排序，这个算法就会取出它的头部，即 5。 将其置于分别比它大和比它小的两个 List 中间，得 `[1,4,3] ++ [5] ++ [9,6,7]`, 我们便知道了当排序结束之时，5会在第四位，因为有3个数比它小，也有三个数比它大。好的，接着排 `[1,4,3]` 与 `[9,6,7]`, 结果就出来了！对它们的排序也是使用同样的函数，将它们分成许多小块，最终到达临界条件，即空 List 经排序依然为空，有个插图：

橙色的部分表示已定位并不再移动的元素。从左到右看，便是一个排过序的 List。在这里我们将所有元素与 `head` 作比较，而实际上就快速排序算法而言，选择任意元素都是可以的。被选择的元素就被称作锚 （`pivot`），以方便模式匹配。小于锚的元素都在浅绿的部分，大于锚都在深绿部分，这个黄黄的坡就表示了快速排序的执行方式：

![](/files/-MlVFe2mrNAXdozPR3Vq)

## 用递归来思考

我们已经写了不少递归了，也许你已经发觉了其中的固定模式：先定义一个边界条件，再定义个函数，让它从一堆元素中取一个并做点事情后，把余下的元素重新交给这个函数。 这一模式对 List、Tree 等数据结构都是适用的。例如，`sum` 函数就是一个 List 头部与其尾部的 `sum` 的和。一个 List 的积便是该 List 的头与其尾部的积相乘的积，一个 List 的长度就是 1 与其尾部长度的和. 等等

![](/files/-LjUmroLAaz62B-9CagR)

再者就是边界条件。一般而言，边界条件就是为避免进程出错而设置的保护措施，处理 List 时的边界条件大部分都是空 List，而处理 Tree 时的边界条件就是没有子元素的节点。

处理数字时也与之相似。函数一般都得接受一个值并修改它。早些时候我们编写过一个计算 Factorial 的函数，它便是某数与它减一的 Factorial 数的积。让它乘以零就不行了， Factorial 数又都是非负数，边界条件便可以定为 1，即乘法的单比特。 因为任何数乘以 1 的结果还是这个数。而在 `sum` 中，加法的单比特就是 0。在快速排序中，边界条件和单比特都是空 List，因为任一 List 与空 List 相加的结果依然是原 List。

使用递归来解决问题时应当先考虑递归会在什么样的条件下不可用, 然后再找出它的边界条件和单比特, 考虑参数应该在何时切开(如对 List 使用模式匹配), 以及在何处执行递归.


# ch06


# 高阶函数

![](/files/-MlVFeAUwfq3udKSakk5)

Haskell 中的函数可以接受函数作为参数也可以返回函数作为结果，这样的函数就被称作高阶函数。高阶函数可不只是某简单特性而已，它贯穿于 Haskell 的方方面面。要拒绝循环与状态的改变而通过定义问题"是什么"来解决问题，高阶函数必不可少。它们是编码的得力工具。

## Curried functions

本质上，Haskell 的所有函数都只有一个参数，那么我们先前编那么多含有多个参数的函数又是怎么回事? 呵，小伎俩! 所有多个参数的函数都是 Curried functions。 什么意思呢? 取一个例子最好理解，就拿我们的好朋友 `max` 函数说事吧。它看起来像是取两个参数，回传较大的那个数。 实际上，执行 `max 4 5` 时，它会首先回传一个取一个参数的函数，其回传值不是 4 就是该参数，取决于谁大。 然后，以 5 为参数调用它，并取得最终结果。 这听着挺绕口的，不过这一概念十分的酷! 如下的两个调用是等价的：

```haskell
ghci> max 4 5
5
ghci> (max 4) 5
5
```

![](/files/MewQuL9BP4dxCrZPXtfR)

把空格放到两个东西之间，称作\_函数调用\_。它有点像个运算符，并拥有最高的优先级。 看看 `max` 函数的型别: `max :: (Ord a) => a -> a -> a`。 也可以写作: `max :: (Ord a) => a -> (a -> a)`。 可以读作 `max` 取一个参数 `a`，并回传一个函数(就是那个 `->`)，这个函数取一个 `a` 型别的参数，回传一个a。 这便是为何只用箭头来分隔参数和回传值型别。

这样的好处又是如何? 简言之，我们若以不全的参数来调用某函数，就可以得到一个\_不全调用\_的函数。 如果你高兴，构造新函数就可以如此便捷，将其传给另一个函数也是同样方便。

看下这个函数，简单至极:

```haskell
multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z
```

我们若执行 `mulThree 3 5 9` 或 `((mulThree 3) 5) 9`，它背后是如何运作呢？ 首先，按照空格分隔，把 `3` 交给 `mulThree`。 这回传一个回传函数的函数。 然后把 `5` 交给它，回传一个取一个参数并使之乘以 `15` 的函数。 最后把 `9` 交给这一函数，回传 `135`。 想想，这个函数的型别也可以写作 `multThree :: (Num a) => a -> (a -> (a -> a))`，`->` 前面的东西就是函数取的参数，后面的东西就是其回传值。所以说，我们的函数取一个 `a`，并回传一个型别为 `(Num a) => a -> (a -> a)` 的函数，类似，这一函数回传一个取一个 `a`，回传一个型别为 `(Num a) => a -> a` 的函数。 而最后的这个函数就只取一个 `a` 并回传一个 `a`，如下:

```haskell
ghci> let multTwoWithNine = multThree 9
ghci> multTwoWithNine 2 3
54
ghci> let multWithEighteen = multTwoWithNine 2
ghci> multWithEighteen 10
180
```

前面提到，以不全的参数调用函数可以方便地创造新的函数。例如，搞个取一数与 100 比较大小的函数该如何? 大可这样:

```haskell
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred x = compare 100 x
```

用 99 调用它，就可以得到一个 `GT`。 简单。 注意下在等号两边都有 `x`。 想想 `compare 100` 会回传什么？一个取一数与 100 比较的函数。 Wow，这不正是我们想要的? 这样重写:

```haskell
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred = compare 100
```

型别声明依然相同，因为 `compare 100` 回传函数。`compare` 的型别为 `(Ord a) => a -> (a -> Ordering)`，用 100 调用它后回传的函数型别为 `(Num a, Ord a) => a -> Ordering`，同时由于 100 还是 `Num` 型别类的实例，所以还得另留一个类约束。

Yo! 你得保证已经弄明白了 Curried functions 与不全调用的原理，它们很重要！

中缀函数也可以不全调用，用括号把它和一边的参数括在一起就行了。 这回传一个取一参数并将其补到缺少的那一端的函数。 一个简单函数如下:

```haskell
divideByTen :: (Floating a) => a -> a
divideByTen = (/10)
```

调用 `divideByTen 200` 就是 `(/10) 200`，和 `200 / 10` 等价。

一个检查字符是否为大写的函数:

```haskell
isUpperAlphanum :: Char -> Bool
isUpperAlphanum = (`elem` ['A'..'Z'])
```

唯一的例外就是 `-` 运算符，按照前面提到的定义，`(-4)` 理应回传一个并将参数减 4 的函数，而实际上，处于计算上的方便，`(-4)` 表示负 `4`。 若你一定要弄个将参数减 4 的函数，就用 `subtract` 好了，像这样 `(subtract 4)`.

若不用 `let` 给它命名或传到另一函数中，在 ghci 中直接执行 `multThree 3 4` 会怎样?

```haskell
ghci> multThree 3 4
:1:0:
No instance for (Show (t -> t))
arising from a use of `print' at :1:0-12
Possible fix: add an instance declaration for (Show (t -> t))
In the expression: print it
In a 'do' expression: print it
```

ghci 说，这一表达式回传了一个 `a -> a` 型别的函数，但它不知道该如何显示它。 函数不是 `Show` 型别类的实例，所以我们不能得到表示一函数内容的字串。 若在 ghci 中计算 `1+1`，它会首先计算得 `2`，然后调用 `show 2` 得到该数值的字串表示，即 `"2"`，再输出到屏幕.

## 是时候了，来点高阶函数！

Haskell 中的函数可以取另一个函数做参数，也可以回传函数。 举个例子，我们弄个取一个函数并调用它两次的函数.

```haskell
applyTwice :: (a -> a) -> a -> a  
applyTwice f x = f (f x)
```

![](/files/-MlVFeAZumHsvpV4iKXQ)

首先注意这型别声明。 在此之前我们很少用到括号，因为 `(->)` 是自然的右结合，不过在这里括号是必须的。 它标明了首个参数是个参数与回传值型别都是a的函数，第二个参数与回传值的型别也都是a。 我们可以用 Curried functions 的思路来理解这一函数，不过免得自寻烦恼，我们姑且直接把它看作是取两个参数回传一个值，其首个参数是个型别为 `(a->a)` 的函数,第二个参数是个 `a`。 该函数的型别可以是 `(Int->Int)`，也可以是 `(String->String)`，但第二个参数必须与之一致。

```
*Note*: 现在开始我们会直说某函数含有多个参数(除非它真的只有一个参数)。 以简洁之名，我们会说 ``(a->a->a)`` 取两个参数，尽管我们知道它在背后做的手脚.
```

这个函数是相当的简单，就拿参数 `f` 当函数，用 `x` 调用它得到的结果再去调用它。也就可以这样玩:

```haskell
ghci> applyTwice (+3) 10  
16  
ghci> applyTwice (++ " HAHA") "HEY"  
"HEY HAHA HAHA"  
ghci> applyTwice ("HAHA " ++) "HEY"  
"HAHA HAHA HEY"  
ghci> applyTwice (multThree 2 2) 9  
144  
ghci> applyTwice (3:) [1]  
[3,3,1]
```

看，不全调用多神奇! 如果有个函数要我们给它传个一元函数，大可以不全调用一个函数让它剩一个参数，再把它交出去。

接下来我们用高阶函数的编程思想来实现个标准库中的函数，它就是 `zipWith`。 它取一个函数和两个 List 做参数，并把两个 List 交到一起(使相应的元素去调用该函数)。 如下就是我们的实现:

```haskell
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]  
zipWith' _ [] _ = []  
zipWith' _ _ [] = []  
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
```

看下这个型别声明，它的首个参数是个函数，取两个参数处理交叉，其型别不必相同，不过相同也没关系。 第二三个参数都是 List，回传值也是个 List。 第一个 List中元素的型别必须是a，因为这个处理交叉的函数的第一个参数是a。 第二个 List 中元素的型别必为 `b`，因为这个处理交叉的函数第二个参数的型别是 `b`。 回传的 List 中元素型别为 `c`。 如果一个函数说取一个型别为 `a->b->c` 的函数做参数，传给它个 `a->a->c` 型别的也是可以的，但反过来就不行了。 可以记下，若在使用高阶函数的时候不清楚其型别为何，就先忽略掉它的型别声明，再到 ghci 下用 `:t` 命令来看下 Haskell 的型别推导.

这函数的行为与普通的 `zip` 很相似，边界条件也是相同，只不过多了个参数，即处理元素交叉的函数。它关不着边界条件什么事儿，所以我们就只留一个 `_`。后一个模式的函数体与 `zip` 也很像，只不过这里是 `f x y` 而非 `(x,y)`。 只要足够通用，一个简单的高阶函数可以在不同的场合反复使用。 如下便是我们 `zipWith'` 函数本领的冰山一角:

```haskell
ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]  
[6,8,7,9]  
ghci> zipWith' max [6,3,2,1] [7,3,1,5]  
[7,3,2,5]  
ghci> zipWith' (++) ["foo ", "bar ", "baz "] ["fighters", "hoppers", "aldrin"]  
["foo fighters","bar hoppers","baz aldrin"]  
ghci> zipWith' (*) (replicate 5 2) [1..]  
[2,4,6,8,10]  
ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]]  
[[3,4,6],[9,20,30],[10,12,12]]
```

如你所见，一个简单的高阶函数就可以玩出很多花样。命令式语言使用 `for`、`while`、赋值、状态检测来实现功能，再包起来留个接口，使之像个函数一样调用。而函数式语言使用高阶函数来抽象出常见的模式，像成对遍历并处理两个 List 或从中筛掉自己不需要的结果。

接下来实现标准库中的另一个函数 `flip`，`flip`简单地取一个函数作参数并回传一个相似的函数，只是它们的两个参数倒了个。

```haskell
flip' :: (a -> b -> c) -> (b -> a -> c)  
flip' f = g  
    where g x y = f y x
```

从这型别声明中可以看出，它取一个函数，其参数型别分别为 `a` 和 `b`，而它回传的函数的参数型别为 `b` 和 `a`。 由于函数缺省都是柯里化的，`->` 为右结合，这里的第二对括号其实并无必要，`(a -> b -> c) -> (b -> a -> c)` 与 `(a -> b -> c) -> (b -> (a -> c))` 等价,也与 `(a -> b -> c) -> b -> a -> c` 等价。 前面我们写了 `g x y = f y x`，既然这样可行，那么 `f y x = g x y` 不也一样? 这一来我们可以改成更简单的写法:

```haskell
flip' :: (a -> b -> c) -> b -> a -> c  
flip' f y x = f x y
```

在这里我们就利用了 Curried functions 的优势，只要调用 `flip' f` 而不带 `y`和`x`，它就会回传一个俩参数倒个的函数。 `flip` 处理的函数往往都是用来传给其他函数调用，于是我们可以发挥 Curried functions 的优势，预先想好发生完全调用的情景并处理好回传值.

```haskell
ghci> flip' zip [1,2,3,4,5] "hello"  
[('h',1),('e',2),('l',3),('l',4),('o',5)]  
ghci> zipWith (flip' div) [2,2..] [10,8,6,4,2]  
[5,4,3,2,1]
```

## map 与 filter

**map** 取一个函数和 List 做参数，遍历该 List 的每个元素来调用该函数产生一个新的 List。 看下它的型别声明和实现:

```haskell
map :: (a -> b) -> [a] -> [b]  
map _ [] = []  
map f (x:xs) = f x : map f xs
```

从这型别声明中可以看出，它取一个取 `a` 回传 `b` 的函数和一组 `a` 的 List，并回传一组 `b`。 这就是 Haskell 的有趣之处：有时只看型别声明就能对函数的行为猜个大致。`map` 函数多才多艺，有一百万种用法。如下是其中一小部分:

```haskell
ghci> map (+3) [1,5,3,1,6]  
[4,8,6,4,9]  
ghci> map (++ "!") ["BIFF", "BANG", "POW"]  
["BIFF!","BANG!","POW!"]  
ghci> map (replicate 3) [3..6]  
[[3,3,3],[4,4,4],[5,5,5],[6,6,6]]  
ghci> map (map (^2)) [[1,2],[3,4,5,6],[7,8]]  
[[1,4],[9,16,25,36],[49,64]]  
ghci> map fst [(1,2),(3,5),(6,3),(2,6),(2,5)]  
[1,3,6,2,2]
```

你可能会发现，以上的所有代码都可以用 List Comprehension 来替代。`map (+3) [1,5,3,1,6]` 与 `[x+3 | x <- [1,5,3,1,6]` 完全等价。

**filter** 函数取一个限制条件和一个 List，回传该 List 中所有符合该条件的元素。它的型别声明及实现大致如下:

```haskell
filter :: (a -> Bool) -> [a] -> [a]  
filter _ [] = []  
filter p (x:xs)   
    | p x       = x : filter p xs  
    | otherwise = filter p xs
```

很简单。只要 `p x` 所得的结果为真，就将这一元素加入新 List，否则就无视之。几个使用范例:

```haskell
ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1]  
[5,6,4]  
ghci> filter (==3) [1,2,3,4,5]  
[3]  
ghci> filter even [1..10]  
[2,4,6,8,10]  
ghci> let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]]  
[[1,2,3],[3,4,5],[2,2]]  
ghci> filter (`elem` ['a'..'z']) "u LaUgH aT mE BeCaUsE I aM diFfeRent"  
"uagameasadifeent"  
ghci> filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same"  
"GAYBALLS"
```

同样，以上都可以用 List Comprehension 的限制条件来实现。并没有教条规定你必须在什么情况下用 `map` 和 `filter` 还是 List Comprehension，选择权归你，看谁舒服用谁就是。 如果有多个限制条件，只能连着套好几个 `filter` 或用 `&&` 等逻辑函数的组合之，这时就不如 List comprehension 来得爽了。

还记得上一章的那个 `quicksort` 函数么? 我们用到了 List Comprehension 来过滤大于或小于锚的元素。 换做 `filter` 也可以实现，而且更加易读：

```haskell
quicksort :: (Ord a) => [a] -> [a]    
quicksort [] = []    
quicksort (x:xs) =     
    let smallerSorted = quicksort (filter (<=x) xs)
        biggerSorted = quicksort (filter (>x) xs)   
    in  smallerSorted ++ [x] ++ biggerSorted
```

![](/files/DNjrJVu4NIiZZOGZNBCq)

`map` 和 `filter` 是每个函数式程序员的面包黄油(呃，`map` 和 `filter` 还是 List Comprehension 并不重要)。 想想前面我们如何解决给定周长寻找合适直角三角形的问题的? 在命令式编程中，我们可以套上三个循环逐个测试当前的组合是否满足条件，若满足，就打印到屏幕或其他类似的输出。 而在函数式编程中，这行就都交给 `map` 和 `filter`。 你弄个取一参数的函数，把它交给 `map` 过一遍 List，再 `filter` 之找到合适的结果。 感谢 Haskell 的惰性，即便是你多次 `map` 一个 \`\`\`List\`\` 也只会遍历一遍该 List，要找出小于 100000 的数中最大的 3829 的倍数，只需过滤结果所在的 List 就行了.

要\_从小于 100000 的数中找出 3829 的最大的倍数\_，我们应当过滤一个结果可能所在的 List.

```haskell
largestDivisible :: (Integral a) => a  
largestDivisible = head (filter p [100000,99999..])  
    where p x = x `mod` 3829 == 0
```

首先，取一个降序的小于 100000 所有数的 List，然后按照限制条件过滤它。 由于这个 List 是降序的，所以结果 List 中的首个元素就是最大的那个数。惰性再次行动! 由于我们只取这结果 List 的首个元素，所以它并不关心这 List 是有限还是无限的，在找到首个合适的结果处运算就停止了。

接下来，我们就要\_找出所有小于 10000 且为奇的平方的和\_，得先提下 **takeWhile** 函数，它取一个限制条件和 List 作参数，然后从头开始遍历这一 List，并回传符合限制条件的元素。 而一旦遇到不符合条件的元素，它就停止了。 如果我们要取出字串 `"elephants know how to party"` 中的首个单词，可以 `takeWhile (/=' ') "elephants know how to party"`，回传 `"elephants"`。okay，要求所有小于 10000 的奇数的平方的和，首先就用 `(^2)` 函数 `map` 掉这个无限的 List `[1..]` 。然后过滤之，只取奇数就是了。 在大于 10000 处将它断开，最后前面的所有元素加到一起。 这一切连写函数都不用，在 ghci 下直接搞定.

```haskell
ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..])))  
166650
```

不错! 先从几个初始数据(表示所有自然数的无限 List)，再 `map` 它，`filter` 它，切它，直到它符合我们的要求，再将其加起来。 这用 List comprehension 也是可以的，而哪种方式就全看你的个人口味.

```haskell
ghci> sum (takeWhile (<10000) [m | m <- [n^2 | n <- [1..]], odd m])  
166650
```

感谢 Haskell 的惰性特质，这一切才得以实现。 我们之所以可以 `map` 或 `filter` 一个无限 List，是因为它的操作不会被立即执行，而是拖延一下。只有我们要求 Haskell 交给我们 `sum` 的结果的时候，`sum` 函数才会跟 `takeWhile` 说，它要这些数。`takeWhile` 就再去要求 `filter` 和 `map` 行动起来，并在遇到大于等于 10000 时候停止. 下个问题与 Collatz 串行有关，取一个自然数，若为偶数就除以 2。 若为奇数就乘以 3 再加 1。 再用相同的方式处理所得的结果，得到一组数字构成的的链。它有个性质，无论任何以任何数字开始，最终的结果都会归 1。所以若拿 13 当作起始数，就可以得到这样一个串行 `13，40，20，10，5，16，8，4，2，1`。`13*3+1` 得 40，40 除 2 得 20，如是继续，得到一个 10 个元素的链。

好的，我们想知道的是: 以 1 到 100 之间的所有数作为起始数，会有多少个链的长度大于 15?

```haskell
chain :: (Integral a) => a -> [a]  
chain 1 = [1]  
chain n  
    | even n =  n:chain (n `div` 2)  
    | odd n  =  n:chain (n*3 + 1)
```

该链止于 1，这便是边界条件。标准的递归函数:

```haskell
ghci> chain 10  
[10,5,16,8,4,2,1]  
ghci> chain 1  
[1]  
ghci> chain 30  
[30,15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]
```

yay! 貌似工作良好。 现在由这个函数来告诉我们结果:

```haskell
numLongChains :: Int  
numLongChains = length (filter isLong (map chain [1..100]))  
    where isLong xs = length xs > 15
```

我们把 `chain` 函数 `map` 到 `[1..100]`，得到一组链的 List，然后用个限制条件过滤长度大于 15 的链。过滤完毕后就可以得出结果list中的元素个数.

```
*Note*: 这函数的型别为 ``numLongChains :: Int``。这是由于历史原因，``length`` 回传一个 ``Int`` 而非 ``Num`` 的成员型别，若要得到一个更通用的 ``Num a``，我们可以使用 ``fromIntegral`` 函数来处理所得结果.
```

用 `map`，我们可以写出类似 `map (*) [0..]` 之类的代码。 如果只是为了例证 Curried functions 和不全调用的函数是真正的值及其原理，那就是你可以把函数传递或把函数装在 List 中(只是你还不能将它们转换为字串)。 迄今为止，我们还只是 `map` 单参数的函数到 List，如 `map (*2) [0..]` 可得一组型别为 `(Num a) => [a]` 的 List，而 `map (*) [0..]` 也是完全没问题的。`*` 的型别为 `(Num a) => a -> a -> a`，用单个参数调用二元函数会回传一个一元函数。如果用 `*` 来 `map` 一个 `[0..]` 的 List，就会得到一组一元函数组成的 List，即 `(Num a) => [a->a]`。`map (*) [0..]` 所得的结果写起来大约就是 `[(0*),(1*),(2*)..]`.

```haskell
ghci> let listOfFuns = map (*) [0..]  
ghci> (listOfFuns !! 4) 5  
20
```

取所得 List 的第五个元素可得一函数，与 `(*4)` 等价。 然后用 `5` 调用它，与 `(* 4) 5` 或 `4*5` 都是等价的.

## lambda

![](/files/cJ00a5949bS00lhm90P8)

lambda 就是匿名函数。有些时候我们需要传给高阶函数一个函数，而这函数我们只会用这一次，这就弄个特定功能的 lambda。编写 lambda，就写个 `\` (因为它看起来像是希腊字母的 lambda -- 如果你斜视的厉害)，后面是用空格分隔的参数，`->` 后面就是函数体。通常我们都是用括号将其括起，要不然它就会占据整个右边部分。

向上 5 英吋左右，你会看到我们在 `numLongChain` 函数中用 `where` 语句声明了个 `isLong` 函数传递给了 `filter`。好的，用 lambda 代替它。

```haskell
numLongChains :: Int  
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))
```

![](/files/3Kqwtm0VRRhByVuagXt8)

lambda 是个表达式，因此我们可以任意传递。表达式 `(\xs -> length xs > 15)` 回传一个函数，它可以告诉我们一个 List 的长度是否大于 15。

不熟悉 Curried functions 与不全调用的人们往往会写出很多 lambda，而实际上大部分都是没必要的。例如，表达式 `map (+3) [1,6,3,2]` 与 `map (\x -> x+3) [1,6,3,2]` 等价，`(+3)` 和 `(\x -> x+3)` 都是给一个数加上 3。不用说，在这种情况下不用 lambda 要清爽的多。

和普通函数一样，lambda 也可以取多个参数。

```haskell
ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]  
[153.0,61.5,31.0,15.75,6.6]
```

同普通函数一样，你也可以在 lambda 中使用模式匹配，只是你无法为一个参数设置多个模式，如 `[]` 和 `(x:xs)`。lambda 的模式匹配若失败，就会引发一个运行时错误，所以慎用！

```haskell
ghci> map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)]  
[3,8,9,8,7]
```

一般情况下，lambda 都是括在括号中，除非我们想要后面的整个语句都作为 lambda 的函数体。很有趣，由于有柯里化，如下的两段是等价的：

```haskell
addThree :: (Num a) => a -> a -> a -> a  
addThree x y z = x + y + z
```

```haskell
addThree :: (Num a) => a -> a -> a -> a  
addThree = \x -> \y -> \z -> x + y + z
```

这样的函数声明与函数体中都有 `->`，这一来型别声明的写法就很明白了。当然第一段代码更易读，不过第二个函数使得柯里化更容易理解。

有些时候用这种语句写还是挺酷的，我觉得这应该是最易读的 `flip` 函数实现了：

```haskell
flip' :: (a -> b -> c) -> b -> a -> c  
flip' f = \x y -> f y x
```

尽管这与 `flip' f x y = f y x` 等价，但它可以更明白地表示出它会产生一个新的函数。`flip` 常用来处理一个函数，再将回传的新函数传递给 `map` 或 `filter`。所以如此使用 lambda 可以更明确地表现出回传值是个函数，可以用来传递给其他函数作参数。

## 关键字 fold

![](/files/gH5yqrLmHFz0fTs2gy5S)

回到当初我们学习递归的情景。我们会发现处理 List 的许多函数都有固定的模式，通常我们会将边界条件设置为空 List，再引入 `(x:xs)` 模式，对单个元素和余下的 List 做些事情。这一模式是如此常见，因此 Haskell 引入了一组函数来使之简化，也就是 `fold`。它们与map有点像，只是它们回传的是单个值。

一个 `fold` 取一个二元函数，一个初始值(我喜欢管它叫累加值)和一个需要折叠的 List。这个二元函数有两个参数，即累加值和 List 的首项(或尾项)，回传值是新的累加值。然后，以新的累加值和新的 List 首项调用该函数，如是继续。到 List 遍历完毕时，只剩下一个累加值，也就是最终的结果。

首先看下 **foldl** 函数，也叫做左折叠。它从 List 的左端开始折叠，用初始值和 List 的头部调用这二元函数，得一新的累加值，并用新的累加值与 List 的下一个元素调用二元函数。如是继续。

我们再实现下 `sum`，这次用 `fold` 替代那复杂的递归：

```haskell
sum' :: (Num a) => [a] -> a  
sum' xs = foldl (\acc x -> acc + x) 0 xs
```

测试下，一二三～

```haskell
ghci> sum' [3,5,2,1]  
11
```

![](/files/-MlVFeAloo1_ANsV7Uzy)

我们深入看下 `fold` 的执行过程：`\acc x-> acc + x` 是个二元函数，`0` 是初始值，`xs` 是待折叠的 List。一开始，累加值为 `0`，当前项为 `3`，调用二元函数 `0+3` 得 `3`，作新的累加值。接着来，累加值为 `3`，当前项为 `5`，得新累加值 `8`。再往后，累加值为 `8`，当前项为 `2`，得新累加值 `10`。最后累加值为 `10`，当前项为 `1`，得 `11`。恭喜，你完成了一次折叠 `(fold)`！

左边的这个图表示了折叠的执行过程，一步又一步(一天又一天!)。浅棕色的数字都是累加值，你可以从中看出 List 是如何从左端一点点加到累加值上的。唔对对对！如果我们考虑到函数的柯里化，可以写出更简单的实现：

```haskell
sum' :: (Num a) => [a] -> a  
sum' = foldl (+) 0
```

这个 lambda 函数 `(\acc x -> acc + x )` 与 `(+)` 等价。我们可以把 `xs` 等一应参数省略掉，反正调用 `foldl (+) 0` 会回传一个取 List 作参数的函数。通常，如果你的函数类似 `foo a = bar b a`， 大可改为 `foo = bar b`。有柯里化嘛。

呼呼，进入右折叠前我们再实现个用到左折叠的函数。大家肯定都知道 `elem` 是检查某元素是否属于某 List 的函数吧，我就不再提了(唔，刚提了)。用左折叠实现它:

```haskell
elem' :: (Eq a) => a -> [a] -> Bool  
elem' y ys = foldl (\acc x -> if x == y then True else acc) False ys
```

好好好，这里我们有什么？起始值与累加值都是布尔值。在处理 `fold` 时，累加值与最终结果的型别总是相同的。如果你不知道怎样对待起始值，那我告诉你，我们先假设它不存在，以 `False` 开始。我们要是 `fold` 一个空 List，结果就是 `False`。然后我们检查当前元素是否为我们寻找的，如果是，就令累加值为 `True`，如果否，就保留原值不变。若 `False`，及表明当前元素不是。若 `True`，就表明已经找到了。

右折叠 **foldr** 的行为与左折叠相似，只是累加值是从 List 的右边开始。同样，左折叠的二元函数取累加值作首个参数，当前值为第二个参数(即 `\acc x -> ...`)，而右折叠的二元函数参数的顺序正好相反(即 `\x acc -> ...`)。这倒也正常，毕竟是从右端开始折叠。

累加值可以是任何型别，可以是数值，布尔值，甚至一个新的 List。我们可以用右 `fold` 实现 `map` 函数，累加值就是个 List。将 `map` 处理过的元素一个一个连到一起。很容易想到，起始值就是空 List。

```haskell
map' :: (a -> b) -> [a] -> [b]  
map' f xs = foldr (\x acc -> f x : acc) [] xs
```

如果我们用 `(+3)` 来映射 `[1,2,3]`，它就会先到达 List 的右端，我们取最后那个元素，也就是 `3` 来调用 `(+3)`，得 `6`。追加 `(:)` 到累加值上，`6:[]` 得 `[6]` 并成为新的累加值。用 `2` 调用 `(+3)`，得 `5`，追加到累加值，于是累加值成了 `[5,6]`。再对 `1` 调用 `(+3)`，并将结果 4 追加到累加值，最终得结果 `[4,5,6]`。

当然，我们也完全可以用左折叠来实现它，`map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs` 就行了。不过问题是，使用 `(++)` 往 List 后面追加元素的效率要比使用 `(:)` 低得多。所以在生成新 List 的时候人们一般都是使用右折叠。

![](/files/-MlVFeAp9X3KuSE4oolI)

反转一个 List，既也可以通过右折叠，也可以通过左折叠。有时甚至不需要管它们的分别，如 `sum` 函数的左右折叠实现都是十分相似。不过有个大的不同，那就是右折叠可以处理无限长度的数据结构，而左折叠不可以。将无限 List 从中断开执行左折叠是可以的，不过若是向右，就永远到不了头了。

*所有遍历 List 中元素并据此回传一个值的操作都可以交给 `fold` 实现*。无论何时需要遍历 List 并回传某值，都可以尝试下 `fold`。因此，`fold`的地位可以说与 `map`和 `filter`并驾齐驱，同为函数式编程中最常用的函数之一。

**foldl1** 与 **foldr1** 的行为与 `foldl` 和 `foldr` 相似，只是你无需明确提供初始值。他们假定 List 的首个(或末尾)元素作为起始值，并从旁边的元素开始折叠。这一来，`sum` 函数大可这样实现：`sum = foldl1 (+)`。这里待折叠的 List 中至少要有一个元素，若使用空 List 就会产生一个运行时错误。不过 `foldl` 和 `foldr` 与空 List 相处的就很好。所以在使用 `fold` 前，应该先想下它会不会遇到空 List，如果不会遇到，大可放心使用 `foldr1` 和 `foldl1`。

为了体会 `fold` 的威力，我们就用它实现几个库函数：

```haskell
maximum' :: (Ord a) => [a] -> a  
maximum' = foldr1 (\x acc -> if x > acc then x else acc)  

reverse' :: [a] -> [a]  
reverse' = foldl (\acc x -> x : acc) []  

product' :: (Num a) => [a] -> a  
product' = foldr1 (*)  

filter' :: (a -> Bool) -> [a] -> [a]  
filter' p = foldr (\x acc -> if p x then x : acc else acc) []  

head' :: [a] -> a  
head' = foldr1 (\x _ -> x)  

last' :: [a] -> a  
last' = foldl1 (\_ x -> x)
```

仅靠模式匹配就可以实现 `head` 函数和 `last` 函数，而且效率也很高。这里只是为了演示，用 `fold` 的实现方法。我觉得我们这个 `reverse'` 定义的相当聪明，用一个空 List 做初始值，并向左展开 List，从左追加到累加值，最后得到一个反转的新 List。`\acc x -> x : acc` 有点像 `:` 函数，只是参数顺序相反。所以我们可以改成 `foldl (flip (:)) []`。

有个理解折叠的思路：假设我们有个二元函数 `f`，起始值 `z`，如果从右折叠 `[3,4,5,6]`，实际上执行的就是 `f 3 (f 4 (f 5 (f 6 z)))`。`f` 会被 List 的尾项和累加值调用，所得的结果会作为新的累加值传入下一个调用。假设 `f` 是 `(+)`，起始值 `z` 是 `0`，那么就是 `3 + (4 + (5 + (6 + 0)))`，或等价的首码形式：`(+) 3 ((+) 4 ((+) 5 ((+) 6 0)))`。相似，左折叠一个 List，以 `g` 为二元函数，`z` 为累加值，它就与 `g (g (g (g z 3) 4) 5) 6` 等价。如果用 `flip (:)` 作二元函数，`[]` 为累加值(看得出，我们是要反转一个 List)，这就与 `flip (:) (flip (:) (flip (:) (flip (:) [] 3) 4) 5) 6` 等价。显而易见，执行该表达式的结果为 `[6,5,4,3]`。

**scanl** 和 **scanr** 与 `foldl` 和 `foldr` 相似，只是它们会记录下累加值的所有状态到一个 List。也有 **scanl1** 和 **scanr1**。

```haskell
ghci> scanl (+) 0 [3,5,2,1]  
[0,3,8,10,11]  
ghci> scanr (+) 0 [3,5,2,1]  
[11,8,3,1,0]  
ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1]  
[3,4,5,5,7,9,9,9]  
ghci> scanl (flip (:)) [] [3,2,1]  
[[],[3],[2,3],[1,2,3]]
```

当使用 `scanl` 时，最终结果就是 List 的最后一个元素。而在 `scanr` 中则是第一个。

```haskell
sqrtSums :: Int  
sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt [1..]))) + 1
```

```haskell
ghci> sqrtSums  
131  
ghci> sum (map sqrt [1..131])  
1005.0942035344083  
ghci> sum (map sqrt [1..130])  
993.6486803921487
```

`scan` 可以用来跟踪 `fold` 函数的执行过程。想想这个问题，*取所有自然数的平方根的和，寻找在何处超过 1000*？ 先`map sqrt [1..]`，然后用个 `fold` 来求它们的和。但在这里我们想知道求和的过程，所以使用 `scan`，`scan` 完毕时就可以得到小于 1000 的所有和。所得结果 List 的第一个元素为 1，第二个就是 1+根2，第三个就是 1+根2+根3。若有 `x` 个和小于 1000，那结果就是 `x+1`。

## 有$的函数调用

好的，接下来看看 **$** 函数。它也叫作\_函数调用符\_。先看下它的定义：

```haskell
($) :: (a -> b) -> a -> b  
f $ x = f x
```

![](/files/-MlVFeB0QfSCLcFe89eu)

什么鬼东西？这没啥意义的操作符？它只是个函数调用符罢了？好吧，不全是，但差不多。普通的函数调用符有最高的优先级，而 `$` 的优先级则最低。用空格的函数调用符是左结合的，如 `f a b c` 与 `((f a) b) c` 等价，而 `$` 则是右结合的。

听着不错。但有什么用？它可以减少我们代码中括号的数目。试想有这个表达式： `sum (map sqrt [1..130])`。由于低优先级的 `$`，我们可以将其改为 `sum $ map sqrt [1..130]`，可以省敲不少键！`sqrt 3 + 4 + 9` 会怎样？这会得到 9，4 和根3 的和。若要取 `(3+4+9)` 的平方根，就得 `sqrt (3+4+9)` 或用 `$`：`sqrt $ 3+4+9`。因为 `$` 有最低的优先级，所以你可以把$看作是在右面写一对括号的等价形式。

`sum (filter (> 10) (map (*2) [2..10]))` 该如何？嗯，`$` 是右结合，`f (g (z x))` 与 `f $ g $ z x` 等价。所以我们可以将 `sum (filter (> 10) (map (*2) [2..10])` 重写为 `sum $ filter (> 10) $ map (*2) [2..10]`。

除了减少括号外，`$` 还可以将数据作为函数使用。例如映射一个函数调用符到一组函数组成的 List：

```haskell
ghci> map ($ 3) [(4+),(10*),(^2),sqrt]  
[7.0,30.0,9.0,1.7320508075688772]
```

## Function composition

在数学中，函数组合是这样定义的： ![](/files/-MlVFeB3xAC4poLu30rZ)，表示组合两个函数成为一个函数。以 `x` 调用这一函数，就与用 `x` 调用 `g` 再用所得的结果调用 `f` 等价。

Haskell 中的函数组合与之很像，即 **.** 函数。其定义为：

```haskell
(.) :: (b -> c) -> (a -> b) -> a -> c  
f . g = \x -> f (g x)
```

![](/files/d0WaTe79Cr3iHyx7EVuk)

注意下这型别声明，`f` 的参数型别必须与 `g` 的回传型别相同。所以得到的组合函数的参数型别与 `g` 相同，回传型别与 `f` 相同。表达式 `negate . (*3)` 回传一个求一数字乘以 3 后的负数的函数。

函数组合的用处之一就是生成新函数，并传递给其它函数。当然我们可以用 lambda 实现，但大多数情况下，使用函数组合无疑更清楚。假设我们有一组由数字组成的 List，要将其全部转为负数，很容易就想到应先取其绝对值，再取负数，像这样：

```haskell
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]
```

注意下这个 lambda 与那函数组合是多么的相像。用函数组合，我们可以将代码改为：

```haskell
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]
```

漂亮！函数组合是右结合的，我们同时组合多个函数。表达式 `f (g (z x))`与 `(f . g . z) x` 等价。按照这个思路，我们可以将

```haskell
ghci> map (\xs -> negate (sum (tail xs))) [[1..5],[3..6],[1..7]]  
[-14,-15,-27]
```

改为：

```haskell
ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]]  
[-14,-15,-27]
```

不过含多个参数的函数该怎么办？好，我们可以使用不全调用使每个函数都只剩下一个参数。`sum (replicate 5 (max 6.7 8.9))` 可以重写为 `(sum . replicate 5 . max 6.7) 8.9` 或 `sum . replicate 5 . max 6.7 $ 8.9`。在这里会产生一个函数，它取与 `max 6.7` 同样的参数，并使用结果调用 `replicate 5` 再用 `sum` 求和。最后用 `8.9` 调用该函数。不过一般你可以这么读，用 8.9 调用 `max 6.7`，然后使它 `replicate 5`，再 `sum` 之。如果你打算用函数组合来替掉那堆括号，可以先在最靠近参数的函数后面加一个 `$`，接着就用 `.` 组合其所有函数调用，而不用管最后那个参数。如果有这样一段代码：`replicate 100 (product (map (*3) (zipWith max [1,2,3,4,5] [4,5,6,7,8])))`，可以改为：`replicate 100 . product . map (*3) . zipWith max [1,2,3,4,5] $ [4,5,6,7,8]`。如果表达式以 3 个括号结尾，就表示你可以将其修改为函数组合的形式。

函数组合的另一用途就是定义 point free style (也称作 pointless style) 的函数。就拿我们之前写的函数作例子：

```haskell
sum' :: (Num a) => [a] -> a     
sum' xs = foldl (+) 0 xs
```

等号的两端都有个 `xs`。由于有柯里化 (Currying)，我们可以省掉两端的 `xs`。`foldl (+) 0` 回传的就是一个取一 List 作参数的函数，我们把它修改为 `sum' = foldl (+) 0`，这就是 point free style。下面这个函数又该如何改成 point free style 呢？

```haskell
fn x = ceiling (negate (tan (cos (max 50 x))))
```

像刚才那样简单去掉两端的 `x` 是不行的，函数定义中 `x` 的右边还有括号。`cos (max 50)` 是有错误的，你不能求一个函数的余弦。我们的解决方法就是，使用函数组合。

```haskell
fn = ceiling . negate . tan . cos . max 50
```

漂亮！point free style 会令你去思考函数的组合方式，而非数据的传递方式，更加简洁明了。你可以将一组简单的函数组合在一起，使之形成一个复杂的函数。不过函数若过于复杂，再使用 point free style 往往会适得其反，因此构造较长的函数组合链是不被鼓励的(虽然我本人热衷于函数组合)。更好的解决方法，就是使用 `let` 语句给中间的运算结果绑定一个名字，或者说把问题分解成几个小问题再组合到一起。这样一来我们代码的读者就可以轻松些，不必要纠结那巨长的函数组合链了。

在 `map` 和 `filter` 那节中，我们求了小于 10000 的所有奇数的平方的和。如下就是将其置于一个函数中的样子：

```haskell
oddSquareSum :: Integer  
oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
```

身为函数组合狂人，我可能会这么写：

```haskell
oddSquareSum :: Integer  
oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..]
```

不过若是给别人看，我可能就这么写了：

```haskell
oddSquareSum :: Integer  
oddSquareSum =   
    let oddSquares = filter odd $ map (^2) [1..]  
        belowLimit = takeWhile (<10000) oddSquares  
    in  sum belowLimit
```

这段代码可赢不了代码花样大赛，不过我们的读者可能会觉得它比函数组合链更好看。


# ch07


# 模块 (Modules)

## 装载模块

![](/files/-MhORBuRDXaHP9ADRH1u)

Haskell 中的模块是含有一组相关的函数，型别和型别类的组合。而 Haskell 进程的本质便是从主模块中引用其它模块并调用其中的函数来执行操作。这样可以把代码分成多块，只要一个模块足够的独立，它里面的函数便可以被不同的进程反复重用。这就让不同的代码各司其职，提高了代码的健壮性。

Haskell 的标准库就是一组模块，每个模块都含有一组功能相近或相关的函数和型别。有处理 List 的模块，有处理并发的模块，也有处理复数的模块，等等。目前为止我们谈及的所有函数,型别以及型别类都是 `Prelude` 模块的一部分，它缺省自动装载。在本章，我们看一下几个常用的模块，在开始浏览其中的函数之前，我们先得知道如何装载模块.

在 Haskell中，装载模块的语法为 `import`，这必须得在函数的定义之前，所以一般都是将它置于代码的顶部。无疑，一段代码中可以装载很多模块，只要将 `import` 语句分行写开即可。装载 `Data.List` 试下，它里面有很多实用的 List 处理函数.

执行 `import Data.List`，这样一来 `Data.List` 中包含的所有函数就都进入了全局命名空间。也就是说，你可以在代码的任意位置调用这些函数.`Data.List` 模块中有个 `nub` 函数，它可以筛掉一个 List 中的所有重复元素。用点号将 `length` 和 `nub` 组合: `length . nub`，即可得到一个与 `(\xs -> length (nub xs))` 等价的函数。

```haskell
import Data.List  

numUniques :: (Eq a) => [a] -> Int  
numUniques = length . nub
```

你也可以在 ghci 中装载模块，若要调用 `Data.List` 中的函数，就这样:

```haskell
ghci> :m Data.List
```

若要在 ghci 中装载多个模块，不必多次 `:m` 命令，一下就可以全部搞定:

```haskell
ghci> :m Data.List Data.Map Data.Set
```

而你的进程中若已经有包含的代码，就不必再用 `:m` 了.

如果你只用得到某模块的两个函数，大可仅包含它俩。若仅装载 `Data.List` 模块 `nub` 和 `sort`，就这样:

```haskell
import Data.List (nub, sort)
```

也可以只包含除去某函数之外的其它函数，这在避免多个模块中函数的命名冲突很有用。假设我们的代码中已经有了一个叫做 `nub` 的函数，而装入 `Data.List` 模块时就要把它里面的 `nub` 除掉.

```haskell
import Data.List hiding (nub)
```

避免命名冲突还有个方法，便是 `qualified import`，`Data.Map` 模块提供一了一个按键索值的数据结构，它里面有几个和 `Prelude` 模块重名的函数。如 `filter` 和 `null`，装入 `Data.Map` 模块之后再调用 `filter`，Haskell 就不知道它究竟是哪个函数。如下便是解决的方法:

```haskell
import qualified Data.Map
```

这样一来，再调用 `Data.Map` 中的 `filter` 函数，就必须得 `Data.Map.filter`，而 `filter` 依然是为我们熟悉喜爱的样子。但是要在每个函数前面都加 `个Data.Map` 实在是太烦人了! 那就给它起个别名，让它短些:

```haskell
import qualified Data.Map as M
```

好，再调用 `Data.Map` 模块的 `filter` 函数的话仅需 `M.filter` 就行了

要浏览所有的标准库模块，参考这个手册。翻阅标准库中的模块和函数是提升个人 Haskell 水平的重要途径。你也可以各个模块的源代码，这对 Haskell 的深入学习及掌握都是大有好处的.

检索函数或搜索函数字置就用 \[<http://www.Haskell.org/hoogle/> Hoogle]，相当了不起的 Haskell 搜索引擎! 你可以用函数名，模块名甚至型别声明来作为检索的条件.

## Data.List

显而易见，`Data.List` 是关于 List 操作的模块，它提供了一组非常有用的 List 处理函数。在前面我们已经见过了其中的几个函数(如 `map` 和 `filter`)，这是 `Prelude` 模块出于方便起见，导出了几个 `Data.List` 里的函数。因为这几个函数是直接引用自 `Data.List`，所以就无需使用 `qualified import`。在下面，我们来看看几个以前没见过的函数:

**intersperse** 取一个元素与 List 作参数，并将该元素置于 List 中每对元素的中间。如下是个例子:

```haskell
ghci> intersperse '.' "MONKEY"  
"M.O.N.K.E.Y"  
ghci> intersperse 0 [1,2,3,4,5,6]  
[1,0,2,0,3,0,4,0,5,0,6]
```

**intercalate** 取两个 List 作参数。它会将第一个 List 交叉插入第二个 List 中间，并返回一个 List.

```haskell
ghci> intercalate " " ["hey","there","guys"]  
"hey there guys"  
ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]  
[1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]
```

**transpose** 函数可以反转一组 List 的 List。你若把一组 List 的 List 看作是个 2D 的矩阵，那 `transpose` 的操作就是将其列为行。

```haskell
ghci> transpose [[1,2,3],[4,5,6],[7,8,9]]  
[[1,4,7],[2,5,8],[3,6,9]]  
ghci> transpose ["hey","there","guys"]  
["htg","ehu","yey","rs","e"]
```

假如有两个多项式 3x2 + 5x + 9，10x3 + 9 和 8x3 + 5x2 + x - 1，将其相加，我们可以列三个 List: `[0,3,5,9]`，`[10,0,0,9]` 和 `[8,5,1,-1]` 来表示。再用如下的方法取得结果.

```haskell
ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]  
[18,8,6,17]
```

![](/files/-MhORBuUa6u_xFe7W5GI)

使用 `transpose` 处理这三个 List 之后，三次幂就到了第一行，二次幂到了第二行，以此类推。在用 `sum` 函数将其映射，即可得到正确的结果。

**foldl'** 和 **foldl1'** 是它们各自惰性实现的严格版本。在用 `fold` 处理较大的 List 时，经常会遇到堆栈溢出的问题。而这罪魁祸首就是 `fold` 的惰性: 在执行 `fold` 时，累加器的值并不会被立即更新，而是做一个"在必要时会取得所需的结果"的承诺。每过一遍累加器，这一行为就重复一次。而所有的这堆"承诺"最终就会塞满你的堆栈。严格的 `fold` 就不会有这一问题，它们不会作"承诺"，而是直接计算中间值的结果并继续执行下去。如果用惰性 `fold` 时经常遇到溢出错误，就应换用它们的严格版。

**concat** 把一组 List 连接为一个 List。

```haskell
ghci> concat ["foo","bar","car"]  
"foobarcar"  
ghci> concat [[3,4,5],[2,3,4],[2,1,1]]  
[3,4,5,2,3,4,2,1,1]
```

它相当于移除一级嵌套。若要彻底地连接其中的元素，你得 `concat` 它两次才行.

**concatMap** 函数与 `map` 一个 List 之后再 `concat` 它等价.

```haskell
ghci> concatMap (replicate 4) [1..3]  
[1,1,1,1,2,2,2,2,3,3,3,3]
```

**and** 取一组布尔值 List 作参数。只有其中的值全为 `True` 的情况下才会返回 `True`。

```haskell
ghci> and $ map (>4) [5,6,7,8]  
True  
ghci> and $ map (==4) [4,4,4,3,4]  
False
```

**or** 与 `and` 相似，一组布尔值 List 中若存在一个 `True` 它就返回 `True`.

```haskell
ghci> or $ map (==4) [2,3,4,5,6,1]  
True  
ghci> or $ map (>4) [1,2,3]  
False
```

**any** 和 **all** 取一个限制条件和一组布尔值 List 作参数，检查是否该 List 的某个元素或每个元素都符合该条件。通常较 `map` 一个 List 到 `and` 或 `or` 而言，使用 `any` 或 `all` 会更多些。

```haskell
ghci> any (==4) [2,3,5,6,1,4]  
True  
ghci> all (>4) [6,9,10]  
True  
ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"  
False  
ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"  
True
```

**iterate** 取一个函数和一个值作参数。它会用该值去调用该函数并用所得的结果再次调用该函数，产生一个无限的 List.

```haskell
ghci> take 10 $ iterate (*2) 1  
[1,2,4,8,16,32,64,128,256,512]  
ghci> take 3 $ iterate (++ "haha") "haha"  
["haha","hahahaha","hahahahahaha"]
```

**splitAt** 取一个 List 和数值作参数，将该 List 在特定的位置断开。返回一个包含两个 List 的二元组.

```haskell
ghci> splitAt 3 "heyman"  
("hey","man")  
ghci> splitAt 100 "heyman"  
("heyman","")  
ghci> splitAt (-3) "heyman"  
("","heyman")  
ghci> let (a,b) = splitAt 3 "foobar" in b ++ a  
"barfoo"
```

**takeWhile** 这一函数十分的实用。它从一个 List 中取元素，一旦遇到不符合条件的某元素就停止.

```haskell
ghci> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]  
[6,5,4]  
ghci> takeWhile (/=' ') "This is a sentence"  
"This"
```

如果要求所有三次方小于 10000 的数的和，用 `filter` 来过滤 `map (^3) [1..]` 所得结果中所有小于 10000 的数是不行的。因为对无限 List 执行的 `filter` 永远都不会停止。你已经知道了这个 List 是单增的，但 Haskell 不知道。所以应该这样：

```haskell
ghci> sum $ takeWhile (<10000) $ map (^3) [1..]  
53361
```

用 `(^3)` 处理一个无限 List，而一旦出现了大于等于 10000 的元素这个 List 就被切断了，sum 到一起也就轻而易举.

**dropWhile** 与此相似，不过它是扔掉符合条件的元素。一旦限制条件返回 `False`，它就返回 List 的余下部分。方便实用!

```haskell
ghci> dropWhile (/=' ') "This is a sentence"  
" is a sentence"  
ghci> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1]  
[3,4,5,4,3,2,1]
```

给一 `Tuple` 组成的 List，这 Tuple 的首项表示股票价格，第二三四项分别表示年,月,日。我们想知道它是在哪天首次突破 $1000 的!

```haskell
ghci> let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3,2008,9,5)]  
ghci> head (dropWhile (\(val,y,m,d) -> val < 1000) stock)  
(1001.4,2008,9,4)
```

**span** 与 `takeWhile` 有点像，只是它返回两个 List。第一个 List 与同参数调用 `takeWhile` 所得的结果相同，第二个 List 就是原 List 中余下的部分。

```haskell
ghci> let (fw, rest) = span (/=' ') "This is a sentence" in "First word:" ++ fw ++ ", the rest:" ++ rest  
"First word: This, the rest: is a sentence"
```

**span** 是在条件首次为 `False` 时断开 List，而 `break` 则是在条件首次为 `True` 时断开 `List`。`break p` 与 `span (not . p)` 是等价的.

```haskell
ghci> break (==4) [1,2,3,4,5,6,7]  
([1,2,3],[4,5,6,7])  
ghci> span (/=4) [1,2,3,4,5,6,7]  
([1,2,3],[4,5,6,7])
```

**break** 返回的第二个 List 就会以第一个符合条件的元素开头。

**sort** 可以排序一个 List，因为只有能够作比较的元素才可以被排序，所以这一 List 的元素必须是 Ord 型别类的实例型别。

```haskell
ghci> sort [8,5,3,2,1,6,4,2]  
[1,2,2,3,4,5,6,8]  
ghci> sort "This will be sorted soon"  
" Tbdeehiillnooorssstw"
```

**group** 取一个 List 作参数，并将其中相邻并相等的元素各自归类，组成一个个子 List.

```haskell
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]  
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
```

若在 `group` 一个 List 之前给它排序就可以得到每个元素在该 List 中的出现次数。

```haskell
ghci> map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]  
[(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]
```

**inits** 和 **tails** 与 `init` 和 `tail` 相似，只是它们会递归地调用自身直到什么都不剩，看:

```haskell
ghci> inits "w00t"  
["","w","w0","w00","w00t"]  
ghci> tails "w00t"  
["w00t","00t","0t","t",""]  
ghci> let w = "w00t" in zip (inits w) (tails w)  
[("","w00t"),("w","00t"),("w0","0t"),("w00","t"),("w00t","")]
```

我们用 `fold` 实现一个搜索子 List 的函数:

```haskell
search :: (Eq a) => [a] -> [a] -> Bool  
search needle haystack =  
  let nlen = length needle  
  in foldl (\acc x -> if take nlen x == needle then True else acc) False (tails haystack)
```

首先，对搜索的 List 调用 `tails`，然后遍历每个 List 来检查它是不是我们想要的.

由此我们便实现了一个类似 **isInfixOf** 的函数，**isInfixOf** 从一个 List 中搜索一个子 List，若该 List 包含子 List，则返回 `True`.

```haskell
ghci> "cat" `isInfixOf` "im a cat burglar"  
True  
ghci> "Cat" `isInfixOf` "im a cat burglar"  
False  
ghci> "cats" `isInfixOf` "im a cat burglar"  
False
```

**isPrefixOf** 与 **isSuffixOf** 分别检查一个 List 是否以某子 List 开头或者结尾.

```haskell
ghci> "hey" `isPrefixOf` "hey there!"  
True  
ghci> "hey" `isPrefixOf` "oh hey there!"  
False  
ghci> "there!" `isSuffixOf` "oh hey there!"  
True  
ghci> "there!" `isSuffixOf` "oh hey there"  
False
```

**elem** 与 **notElem** 检查一个 List 是否包含某元素.

**partition** 取一个限制条件和 List 作参数，返回两个 List，第一个 List 中包含所有符合条件的元素，而第二个 List 中包含余下的.

```haskell
ghci> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"  
("BOBMORGAN","sidneyeddy")  
ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7]  
([5,6,7],[1,3,3,2,1,0,3])
```

了解这个与 `span` 和 `break` 的差异是很重要的.

```haskell
ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"  
("BOB","sidneyMORGANeddy")
```

`span` 和 `break` 会在遇到第一个符合或不符合条件的元素处断开，而 `partition` 则会遍历整个 List。

**find** 取一个 List 和限制条件作参数，并返回首个符合该条件的元素，而这个元素是个 `Maybe` 值。在下章，我们将深入地探讨相关的算法和数据结构，但在这里你只需了解 `Maybe` 值是 `Just something` 或 `Nothing` 就够了。与一个 List 可以为空也可以包含多个元素相似，一个 `Maybe` 可以为空，也可以是单一元素。同样与 List 类似，一个 Int 型的 List 可以写作 `[Int]`，`Maybe`有个 Int 型可以写作 `Maybe Int`。先试一下 `find` 函数再说.

```haskell
ghci> find (>4) [1,2,3,4,5,6]  
Just 5  
ghci> find (>9) [1,2,3,4,5,6]  
Nothing  
ghci> :t find  
find :: (a -> Bool) -> [a] -> Maybe a
```

注意一下 `find` 的型别，它的返回结果为 `Maybe a`，这与 `[a]` 的写法有点像，只是 `Maybe` 型的值只能为空或者单一元素，而 List 可以为空,一个元素，也可以是多个元素.

想想前面那段找股票的代码，`head (dropWhile (\(val,y,m,d) -> val < 1000) stock)` 。但 `head` 并不安全! 如果我们的股票没涨过 $1000 会怎样? `dropWhile` 会返回一个空 List，而对空 List 取 `head` 就会引发一个错误。把它改成 `find (\(val,y,m,d) -> val > 1000) stock` 就安全多啦，若存在合适的结果就得到它, 像 `Just (1001.4,2008,9,4)`，若不存在合适的元素(即我们的股票没有涨到过 $1000)，就会得到一个 `Nothing`.

**elemIndex** 与 `elem` 相似，只是它返回的不是布尔值，它只是'可能' (Maybe)返回我们找的元素的索引，若这一元素不存在，就返回 `Nothing`。

```haskell
ghci> :t elemIndex  
elemIndex :: (Eq a) => a -> [a] -> Maybe Int  
ghci> 4 `elemIndex` [1,2,3,4,5,6]  
Just 3  
ghci> 10 `elemIndex` [1,2,3,4,5,6]  
Nothing
```

**elemIndices** 与 `elemIndex` 相似，只不过它返回的是 List，就不需要 `Maybe` 了。因为不存在用空 List 就可以表示，这就与 `Nothing` 相似了.

```haskell
ghci> ' ' `elemIndices` "Where are the spaces?"  
[5,9,13]
```

**findIndex** 与 `find` 相似，但它返回的是可能存在的首个符合该条件元素的索引。**findIndices** 会返回所有符合条件的索引.

```haskell
ghci> findIndex (==4) [5,3,2,1,6,4]  
Just 5  
ghci> findIndex (==7) [5,3,2,1,6,4]  
Nothing  
ghci> findIndices (`elem` ['A'..'Z']) "Where Are The Caps?"  
[0,6,10,14]
```

在前面，我们讲过了 `zip` 和 `zipWith`，它们只能将两个 List 组到一个二元组数或二参函数中，但若要组三个 List 该怎么办? 好说\~ 有 `zip3`,`zip4`...,和 `zipWith3`, `zipWith4`...直到 7。这看起来像是个 hack，但工作良好。连着组 8 个 List 的情况很少遇到。还有个聪明办法可以组起无限多个 List，但限于我们目前的水平，就先不谈了.

```haskell
ghci> zipWith3 (\x y z -> x + y + z) [1,2,3] [4,5,2,2] [2,2,3]  
[7,9,8]  
ghci> zip4 [2,3,3] [2,2,2] [5,5,3] [2,2,2]  
[(2,2,5,2),(3,2,5,2),(3,2,3,2)]
```

与普通的 `zip` 操作相似，以返回的 List 中长度最短的那个为准.

在处理来自文件或其它地方的输入时，**lines** 会非常有用。它取一个字串作参数。并返回由其中的每一行组成的 List.

```haskell
ghci> lines "first line\nsecond line\nthird line"  
["first line","second line","third line"]
```

`'\n'` 表示unix下的换行符，在 Haskell 的字符中，反斜杠表示特殊字符.

**unlines** 是 `lines` 的反函数，它取一组字串的 List，并将其通过 `'\n'`合并到一块.

```haskell
ghci> unlines ["first line", "second line", "third line"]  
"first line\nsecond line\nthird line\n"
```

**words** 和 **unwords** 可以把一个字串分为一组单词或执行相反的操作，很有用.

```haskell
ghci> words "hey these are the words in this sentence"  
["hey","these","are","the","words","in","this","sentence"]  
ghci> words "hey these are the words in this\nsentence"  
["hey","these","are","the","words","in","this","sentence"]  
ghci> unwords ["hey","there","mate"]  
"hey there mate"
```

我们前面讲到了 **nub**，它可以将一个 List 中的重复元素全部筛掉，使该 List 的每个元素都如雪花般独一无二，'nub' 的含义就是'一小块'或'一部分'，用在这里觉得很古怪。我觉得，在函数的命名上应该用更确切的词语，而避免使用老掉牙的过时词汇.

```haskell
ghci> nub [1,2,3,4,3,2,1,2,3,4,3,2,1]  
[1,2,3,4]  
ghci> nub "Lots of words and stuff"  
"Lots fwrdanu"
```

**delete** 取一个元素和 List 作参数，会删掉该 List 中首次出现的这一元素.

```haskell
ghci> delete 'h' "hey there ghang!"  
"ey there ghang!"  
ghci> delete 'h' . delete 'h' $ "hey there ghang!"  
"ey tere ghang!"  
ghci> delete 'h' . delete 'h' . delete 'h' $ "hey there ghang!"  
"ey tere gang!"
```

**\\** 表示 List 的差集操作，这与集合的差集很相似，它会从左边 List 中的元素扣除存在于右边 List 中的元素一次.

```haskell
ghci> [1..10] \\ [2,5,9]  
[1,3,4,6,7,8,10]  
ghci> "Im a big baby" \\ "big"  
"Im a  baby"
```

**union** 与集合的并集也是很相似，它返回两个 List 的并集，即遍历第二个 List 若存在某元素不属于第一个 List，则追加到第一个 List。看，第二个 List 中的重复元素就都没了!

```haskell
ghci> "hey man" `union` "man what's up"  
"hey manwt'sup"  
ghci> [1..7] `union` [5..10]  
[1,2,3,4,5,6,7,8,9,10]
```

**intersect** 相当于集合的交集。它返回两个 List 的相同部分.

```haskell
ghci> [1..7] `intersect` [5..10]  
[5,6,7]
```

**insert** 可以将一个元素插入一个可排序的 List，并将其置于首个大于等于它的元素之前，如果使用 `insert` 来给一个排过序的 List 插入元素，返回的结果依然是排序的.

```haskell
ghci> insert 4 [1,2,3,5,6,7]  
[1,2,3,4,5,6,7]  
ghci> insert 'g' $ ['a'..'f'] ++ ['h'..'z']  
"abcdefghijklmnopqrstuvwxyz"  
ghci> insert 3 [1,2,4,3,2,1]  
[1,2,3,4,3,2,1]
```

`length`，`take`，`drop`，`splitAt`，`!!` 和 `replicate` 之类的函数有个共同点。那就是它们的参数中都有个 Int 值（或者返回Int值），我觉得使用 Intergal 或 Num 型别类会更好，但出于历史原因，修改这些会破坏掉许多既有的代码。在 `Data.List` 中包含了更通用的替代版，如: `genericLength，genericTake，genericDrop，genericSplitAt，genericIndex` 和 `genericReplicate`。`length` 的型别声明为 `length :: [a] -> Int`，而我们若要像这样求它的平均值，`let xs = [1..6] in sum xs / length xs` ，就会得到一个型别错误，因为 `/` 运算符不能对 Int 型使用! 而 `genericLength` 的型别声明则为 `genericLength :: (Num a) => [b] -> a`，Num 既可以是整数又可以是浮点数，`let xs = [1..6] in sum xs / genericLength xs` 这样再求平均数就不会有问题了.

`nub`, `delete`, `union`, `intsect` 和 `group` 函数也有各自的通用替代版 `nubBy`，`deleteBy`，`unionBy`，`intersectBy` 和 `groupBy`，它们的区别就是前一组函数使用 `(==)` 来测试是否相等，而带 `By` 的那组则取一个函数作参数来判定相等性，`group` 就与 `groupBy (==)` 等价.

假如有个记录某函数在每秒的值的 List，而我们要按照它小于零或者大于零的交界处将其分为一组子 List。如果用 `group`，它只能将相邻并相等的元素组到一起，而在这里我们的标准是它们是否互为相反数。`groupBy` 登场! 它取一个含两个参数的函数作为参数来判定相等性.

```haskell
ghci> let values = [-4.3,-2.4,-1.2,0.4,2.3,5.9,10.5,29.1,5.3,-2.4,-14.5,2.9,2.3]  
ghci> groupBy (\x y -> (x > 0) == (y > 0)) values  
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
```

这样一来我们就可以很清楚地看出哪部分是正数，哪部分是负数，这个判断相等性的函数会在两个元素同时大于零或同时小于零时返回 `True`。也可以写作 `\x y -> (x > 0) && (y > 0) || (x <= 0) && (y <= 0)`。但我觉得第一个写法的可读性更高。`Data.Function` 中还有个 `on` 函数可以让它的表达更清晰，其定义如下:

```haskell
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c  
f `on` g = \x y -> f (g x) (g y)
```

执行 ``(==) `on` (> 0)`` 得到的函数就与 `\x y -> (x > 0) == (y > 0)` 基本等价。`on` 与带 `By` 的函数在一起会非常好用，你可以这样写:

```haskell
ghci> groupBy ((==) `on` (> 0)) values  
[[-4.3,-2.4,-1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[-2.4,-14.5],[2.9,2.3]]
```

可读性很高! 你可以大声念出来: 按照元素是否大于零，给它分类！

同样，`sort`，`insert`，`maximum` 和 `min` 都有各自的通用版本。如 `groupBy` 类似，**sortBy**，**insertBy**，**maximumBy** 和 **minimumBy** 都取一个函数来比较两个元素的大小。像 `sortBy` 的型别声明为: `sortBy :: (a -> a -> Ordering) -> [a] -> [a]`。前面提过，`Ordering` 型别可以有三个值,`LT`，`EQ` 和 `GT`。`compare` 取两个 `Ord` 型别类的元素作参数，所以 `sort` 与 `sortBy compare` 等价.

List 是可以比较大小的，且比较的依据就是其中元素的大小。如果按照其子 List 的长度为标准当如何? 很好，你可能已经猜到了，`sortBy` 函数.

```haskell
ghci> let xs = [[5,4,5,4,4],[1,2,3],[3,5,4,3],[],[2],[2,2]]  
ghci> sortBy (compare `on` length) xs  
[[],[2],[2,2],[1,2,3],[3,5,4,3],[5,4,5,4,4]]
```

太绝了! ``compare `on` length``，乖乖，这简直就是英文! 如果你搞不清楚 `on` 在这里的原理，就可以认为它与 ``\x y -> length x `compare` length y`` 等价。通常，与带 `By` 的函数打交道时，若要判断相等性，则 ``(==) `on` something``。若要判定大小，则 ``compare `on` something``.

## Data.Char

如其名，`Data.Char` 模块包含了一组用于处理字符的函数。由于字串的本质就是一组字符的 List，所以往往会在 `filter` 或是 `map` 字串时用到它.

`Data.Char`模块中含有一系列用于判定字符范围的函数，如下:

![](/files/iHZsf9R7IAuX3Z1aXCY9)

**isControl** 判断一个字符是否是控制字符。 **isSpace** 判断一个字符是否是空格字符，包括空格，tab，换行符等. **isLower** 判断一个字符是否为小写. **isUper** 判断一个字符是否为大写。 **isAlpha** 判断一个字符是否为字母. **isAlphaNum** 判断一个字符是否为字母或数字. **isPrint** 判断一个字符是否是可打印的. **isDigit** 判断一个字符是否为数字. **isOctDigit** 判断一个字符是否为八进制数字. **isHexDigit** 判断一个字符是否为十六进制数字. **isLetter** 判断一个字符是否为字母. **isMark** 判断是否为 unicode 注音字符，你如果是法国人就会经常用到的. **isNumber** 判断一个字符是否为数字. **isPunctuation** 判断一个字符是否为标点符号. **isSymbol**判断一个字符是否为货币符号. **isSeperater** 判断一个字符是否为 unicode 空格或分隔符. **isAscii** 判断一个字符是否在 unicode 字母表的前 128 位。 **isLatin1** 判断一个字符是否在 unicode 字母表的前 256 位. **isAsciiUpper** 判断一个字符是否为大写的 ascii 字符. **isAsciiLower** 判断一个字符是否为小写的 ascii 字符.

以上所有判断函数的型别声明皆为 `Char -> Bool`，用到它们的绝大多数情况都无非就是过滤字串或类似操作。假设我们在写个进程，它需要一个由字符和数字组成的用户名。要实现对用户名的检验，我们可以结合使用 `Data.List` 模块的 `all` 函数与 `Data.Char` 的判断函数.

```haskell
ghci> all isAlphaNum "bobby283"  
True  
ghci> all isAlphaNum "eddy the fish!"  
False
```

Kewl\~ 免得你忘记，`all` 函数取一个判断函数和一个 List 做参数，若该 List 的所有元素都符合条件，就返回 `True`.

也可以使用 `isSpace` 来实现 `Data.List` 的 `words` 函数.

```haskell
ghci> words "hey guys its me"  
["hey","guys","its","me"]  
ghci> groupBy ((==) `on` isSpace) "hey guys its me"  
["hey"," ","guys"," ","its"," ","me"]  
ghci>
```

Hmm，不错，有点 `words` 的样子了。只是还有空格在里面，恩，该怎么办? 我知道，用 `filter` 滤掉它们!

```haskell
ghci> filter (not . any isSpace) . groupBy ((==) `on` isSpace) $ "hey guys its me"  
["hey","guys","its","me"]
```

啊哈.

`Data.Char` 中也含有与 `Ordering` 相似的型别。`Ordering` 可以有三个值，`LT`，`GT` 和 `EQ`。这就是个枚举，它表示了两个元素作比较可能的结果. `GeneralCategory` 型别也是个枚举，它表示了一个字符可能所在的分类。而得到一个字符所在分类的主要方法就是使用 `generalCategory` 函数.它的型别为: `generalCategory :: Char -> GeneralCategory`。那 31 个分类就不在此一一列出了，试下这个函数先:

```haskell
ghci> generalCategory ' '  
Space  
ghci> generalCategory 'A'  
UppercaseLetter  
ghci> generalCategory 'a'  
LowercaseLetter  
ghci> generalCategory '.'  
OtherPunctuation  
ghci> generalCategory '9'  
DecimalNumber  
ghci> map generalCategory " \t\nA9?|"  
[Space,Control,Control,UppercaseLetter,DecimalNumber,OtherPunctuation,MathSymbol]
```

由于 `GeneralCategory` 型别是 `Eq` 型别类的一部分，使用类似 `generalCategory c == Space` 的代码也是可以的.

**toUpper** 将一个字符转为大写字母，若该字符不是小写字母，就按原值返回. **toLower** 将一个字符转为小写字母，若该字符不是大写字母，就按原值返回. **toTitle** 将一个字符转为 title-case，对大多数字元而言，title-case 就是大写. **digitToInt** 将一个字符转为 Int 值，而这一字符必须得在 `'1'..'9','a'..'f'`或`'A'..'F'` 的范围之内.

```haskell
ghci> map digitToInt "34538"  
[3,4,5,3,8]  
ghci> map digitToInt "FF85AB"  
[15,15,8,5,10,11]
```

`intToDigit` 是 `digitToInt` 的反函数。它取一个 `0` 到 `15` 的 `Int` 值作参数，并返回一个小写的字符.

```haskell
ghci> intToDigit 15  
'f'  
ghci> intToDigit 5  
'5'
```

**ord** 与 **char** 函数可以将字符与其对应的数字相互转换.

```haskell
ghci> ord 'a'  
97  
ghci> chr 97  
'a'  
ghci> map ord "abcdefgh"  
[97,98,99,100,101,102,103,104]
```

两个字符的 `ord` 值之差就是它们在 unicode 字符表上的距离.

*Caesar ciphar* 是加密的基础算法，它将消息中的每个字符都按照特定的字母表进行替换。它的实现非常简单，我们这里就先不管字母表了.

```haskell
encode :: Int -> String -> String  
encode shift msg = 
  let ords = map ord msg  
      shifted = map (+ shift) ords  
  in map chr shifted
```

先将一个字串转为一组数字，然后给它加上某数，再转回去。如果你是标准的组合牛仔，大可将函数写为: `map (chr . (+ shift) . ord) msg`。试一下它的效果:

```haskell
ghci> encode 3 "Heeeeey"  
"Khhhhh|"  
ghci> encode 4 "Heeeeey"  
"Liiiii}"  
ghci> encode 1 "abcd"  
"bcde"  
ghci> encode 5 "Marry Christmas! Ho ho ho!"  
"Rfww~%Hmwnxyrfx&%Mt%mt%mt&"
```

不错。再简单地将它转成一组数字，减去某数后再转回来就是解密了.

```haskell
decode :: Int -> String -> String  
decode shift msg = encode (negate shift) msg
```

```haskell
ghci> encode 3 "Im a little teapot"  
"Lp#d#olwwoh#whdsrw"  
ghci> decode 3 "Lp#d#olwwoh#whdsrw"  
"Im a little teapot"  
ghci> decode 5 . encode 5 $ "This is a sentence"  
"This is a sentence"
```

## Data.Map

关联列表(也叫做字典)是按照键值对排列而没有特定顺序的一种 List。例如，我们用关联列表保存电话号码，号码就是值，人名就是键。我们并不关心它们的存储顺序，只要能按人名得到正确的号码就好.在 Haskell 中表示关联列表的最简单方法就是弄一个二元组的 List，而这二元组就首项为键，后项为值。如下便是个表示电话号码的关联列表:

```haskell
phoneBook = [("betty","555-2938") ,
             ("bonnie","452-2928") ,
             ("patsy","493-2928") ,
             ("lucille","205-2928") ,
             ("wendy","939-8282") ,
             ("penny","853-2492") ]
```

不理这貌似古怪的缩进，它就是一组二元组的 List 而已。话说对关联列表最常见的操作就是按键索值，我们就写个函数来实现它。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> v 
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
```

![](/files/9oGtcmxHcCcOhFeQ2nIH)

简洁漂亮。这个函数取一个键和 List 做参数，过滤这一 List 仅保留键匹配的项，并返回首个键值对。但若该关联列表中不存在这个键那会怎样? 哼，那就会在试图从空 List 中取 `head` 时引发一个运行时错误。无论如何也不能让进程就这么轻易地崩溃吧，所以就应该用 `Maybe` 型别。如果没找到相应的键，就返回 `Nothing`。而找到了就返回 `Just something`。而这 `something` 就是键对应的值。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v 
findKey key [] = Nothing
findKey key ((k,v):xs) = 
     if key == k then 
         Just v 
     else 
         findKey key xs
```

看这型别声明，它取一个可判断相等性的键和一个关联列表做参数，可能 (Maybe) 得到一个值。听起来不错.这便是个标准的处理 List 的递归函数，边界条件，分割 List，递归调用，都有了 -- 经典的 `fold` 模式。 看看用 `fold` 怎样实现吧。

```haskell
findKey :: (Eq k) => k -> [(k,v)] -> Maybe v 
findKey key = foldr (\(k,v) acc -> if key == k then Just v else acc) Nothing
```

```
*Note*: 通常，使用 ``fold`` 来替代类似的递归函数会更好些。用 ``fold`` 的代码让人一目了然，而看明白递归则得多花点脑子。
```

```haskell
ghci> findKey "penny" phoneBook 
Just "853-2492" 
ghci> findKey "betty" phoneBook 
Just "555-2938" 
ghci> findKey "wilma" phoneBook 
Nothing
```

如魔咒般灵验! 只要我们有这姑娘的号码就 `Just` 可以得到，否则就是 `Nothing`. 方才我们实现的函数便是 `Data.List` 模块的 `lookup`，如果要按键去寻找相应的值，它就必须得遍历整个 List，直到找到为止。而 `Data.Map` 模块提供了更高效的方式(通过树实现)，并提供了一组好用的函数。从现在开始，我们扔掉关联列表，改用map.由于`Data.Map`中的一些函数与Prelude和`Data.List` 模块存在命名冲突，所以我们使用 `qualified import`。`import qualified Data.Map as Map` 在代码中加上这句，并 `load` 到 ghci 中.继续前进，看看 `Data.Map` 是如何的一座宝库! 如下便是其中函数的一瞥:

**fromList** 取一个关联列表，返回一个与之等价的 Map。

```haskell
ghci> Map.fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] 
fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928")] 
ghci> Map.fromList [(1,2),(3,4),(3,2),(5,5)] 
fromList [(1,2),(3,2),(5,5)]
```

若其中存在重复的键,就将其忽略。如下即 `fromList` 的型别声明。

```haskell
Map.fromList :: (Ord k) => [(k,v)] -> Map.Map k v
```

这表示它取一组键值对的 List，并返回一个将 `k` 映射为 `v` 的 `map`。注意一下，当使用普通的关联列表时，只需要键的可判断相等性就行了。而在这里，它还必须得是可排序的。这在 `Data.Map` 模块中是强制的。因为它会按照某顺序将其组织在一棵树中.在处理键值对时，只要键的型别属于 `Ord` 型别类，就应该尽量使用`Data.Map`.`empty` 返回一个空 `map`.

```haskell
ghci> Map.empty 
fromList []
```

**insert** 取一个键，一个值和一个 `map` 做参数，给这个 `map` 插入新的键值对，并返回一个新的 `map`。

```haskell
ghci> Map.empty 
fromList [] 
ghci> Map.insert 3 100 Map.empty
fromList [(3,100)] 
ghci> Map.insert 5 600 (Map.insert 4 200 ( Map.insert 3 100  Map.empty)) 
fromList [(3,100),(4,200),(5,600)]
ghci> Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty 
fromList [(3,100),(4,200),(5,600)]
```

通过 `empty`，`insert` 与 `fold`，我们可以编写出自己的 `fromList`。

```haskell
fromList' :: (Ord k) => [(k,v)] -> Map.Map k v 
fromList' = foldr (\(k,v) acc -> Map.insert k v acc) Map.empty
```

简洁明了的 `fold`！ 从一个空的 `map` 开始，然后从右折叠，随着遍历不断地往 `map` 中插入新的键值对.

**null** 检查一个 `map` 是否为空.

```haskell
ghci> Map.null Map.empty 
True 
ghci> Map.null $ Map.fromList [(2,3),(5,5)] 
False
```

**size** 返回一个 `map` 的大小。

```haskell
ghci> Map.size Map.empty 
0 
ghci> Map.size $ Map.fromList [(2,4),(3,3),(4,2),(5,4),(6,4)] 
5
```

**singleton** 取一个键值对做参数,并返回一个只含有一个映射的 `map`.

```haskell
ghci> Map.singleton 3 9 
fromList [(3,9)] 
ghci> Map.insert 5 9 $ Map.singleton 3 9 
fromList [(3,9),(5,9)]
```

**lookup** 与 `Data.List` 的 `lookup` 很像,只是它的作用对象是 `map`，如果它找到键对应的值。就返回 `Just something`，否则返回 `Nothing`。

**member** 是个判断函数，它取一个键与 `map` 做参数，并返回该键是否存在于该 `map`。

```haskell
ghci> Map.member 3 $ Map.fromList [(3,6),(4,3),(6,9)] 
True 
ghci> Map.member 3 $ Map.fromList [(2,5),(4,5)] 
False
```

**map** 与 **filter** 与其对应的 `List` 版本很相似:

```haskell
ghci> Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)] 
fromList [(1,100),(2,400),(3,900)] 
ghci> Map.filter isUpper $ Map.fromList [(1,'a'),(2,'A'),(3,'b'),(4,'B')] 
fromList [(2,'A'),(4,'B')]
```

`toList` 是 `fromList` 的反函数。

```haskell
ghci> Map.toList . Map.insert 9 2 $ Map.singleton 4 3 
[(4,3),(9,2)]
```

**keys** 与 **elems** 各自返回一组由键或值组成的 List，`keys` 与 `map fst . Map.toList` 等价，`elems` 与 `map snd . Map.toList`等价. `fromListWith` 是个很酷的小函数，它与 `fromList` 很像，只是它不会直接忽略掉重复键，而是交给一个函数来处理它们。假设一个姑娘可以有多个号码，而我们有个像这样的关联列表:

```haskell
phoneBook =   
    [("betty","555-2938")  
    ,("betty","342-2492")  
    ,("bonnie","452-2928")  
    ,("patsy","493-2928")  
    ,("patsy","943-2929")  
    ,("patsy","827-9162")  
    ,("lucille","205-2928")  
    ,("wendy","939-8282")  
    ,("penny","853-2492")  
    ,("penny","555-2111")  
    ]
```

如果用 `fromList` 来生成 `map`，我们会丢掉许多号码! 如下才是正确的做法:

```haskell
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String  
phoneBookToMap xs = Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) xs
```

```haskell
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook 
"827-9162, 943-2929, 493-2928" 
ghci> Map.lookup "wendy" $ phoneBookToMap phoneBook
"939-8282" 
ghci> Map.lookup "betty" $ phoneBookToMap phoneBook 
"342-2492,555-2938"
```

一旦出现重复键，这个函数会将不同的值组在一起，同样，也可以缺省地将每个值放到一个单元素的 List 中，再用 `++` 将他们都连接在一起。

```haskell
phoneBookToMap :: (Ord k) => [(k,a)] -> Map.Map k [a] 
phoneBookToMap xs = Map.fromListWith (++) $ map (\(k,v) -> (k,[v])) xs 
ghci> Map.lookup "patsy" $ phoneBookToMap phoneBook 
["827-9162","943-2929","493-2928"]
```

很简洁! 它还有别的玩法，例如在遇到重复元素时，单选最大的那个值.

```haskell
ghci> Map.fromListWith max [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)] 
fromList [(2,100),(3,29),(4,22)]
```

或是将相同键的值都加在一起.

```haskell
ghci> Map.fromListWith (+) [(2,3),(2,5),(2,100),(3,29),(3,22),(3,11),(4,22),(4,15)] 
fromList [(2,108),(3,62),(4,37)]
```

**insertWith** 之于 `insert`，恰如 `fromListWith` 之于 `fromList`。它会将一个键值对插入一个 `map` 之中，而该 `map` 若已经包含这个键，就问问这个函数该怎么办。

```haskell
ghci> Map.insertWith (+) 3 100 $ Map.fromList [(3,4),(5,103),(6,339)] 
fromList [(3,104),(5,103),(6,339)]
```

`Data.Map` 里面还有不少函数，\[<http://www.haskell.org/ghc/docs/latest/html/libraries/containers/Data-Map.html> 这个文档]中的列表就很全了.

## Data.Set

![](/files/Tauht7xibB8wVcrsfTAs)

`Data.Set` 模块提供了对数学中集合的处理。集合既像 List 也像 `Map`: 它里面的每个元素都是唯一的，且内部的数据由一棵树来组织(这和 `Data.Map` 模块的 `map` 很像)，必须得是可排序的。同样是插入,删除,判断从属关系之类的操作，使用集合要比 List 快得多。对一个集合而言，最常见的操作莫过于并集，判断从属或是将集合转为 List.

由于 `Data.Set` 模块与 `Prelude` 模块和 `Data.List` 模块中存在大量的命名冲突，所以我们使用 `qualified import`

将 `import` 语句至于代码之中:

```haskell
import qualified Data.Set as Set
```

然后在 ghci 中装载

假定我们有两个字串，要找出同时存在于两个字串的字符

```haskell
text1 = "I just had an anime dream. Anime... Reality... Are they so different?"  
text2 = "The old man left his garbage can out and now his trash is all over my lawn!"
```

**fromList** 函数同你想的一样，它取一个 List 作参数并将其转为一个集合

```haskell
ghci> let set1 = Set.fromList text1  
ghci> let set2 = Set.fromList text2  
ghci> set1  
fromList " .?AIRadefhijlmnorstuy"  
ghci> set2  
fromList " !Tabcdefghilmnorstuvwy"
```

如你所见，所有的元素都被排了序。而且每个元素都是唯一的。现在我们取它的交集看看它们共同包含的元素:

```haskell
ghci> Set.intersection set1 set2  
fromList " adefhilmnorstuy"
```

使用 `difference` 函数可以得到存在于第一个集合但不在第二个集合的元素

```haskell
ghci> Set.difference set1 set2  
fromList ".?AIRj"  
ghci> Set.difference set2 set1  
fromList "!Tbcgvw"
```

也可以使用 `union` 得到两个集合的并集

```haskell
ghci> Set.union set1 set2  
fromList " !.?AIRTabcdefghijlmnorstuvwy"
```

`null`，`size`，`member`，`empty`，`singleton`，`insert`，`delete` 这几个函数就跟你想的差不多啦

```haskell
ghci> Set.null Set.empty  
True  
ghci> Set.null $ Set.fromList [3,4,5,5,4,3]  
False  
ghci> Set.size $ Set.fromList [3,4,5,3,4,5]  
3  
ghci> Set.singleton 9  
fromList [9]  
ghci> Set.insert 4 $ Set.fromList [9,3,8,1]  
fromList [1,3,4,8,9]  
ghci> Set.insert 8 $ Set.fromList [5..10]  
fromList [5,6,7,8,9,10]  
ghci> Set.delete 4 $ Set.fromList [3,4,5,4,3,4,5]  
fromList [3,5]
```

也可以判断子集与真子集，如果集合 A 中的元素都属于集合 B，那么 A 就是 B 的子集, 如果 A 中的元素都属于 B 且 B 的元素比 A 多，那 A 就是 B 的真子集

```haskell
ghci> Set.fromList [2,3,4] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
True  
ghci> Set.fromList [1,2,3,4,5] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
True  
ghci> Set.fromList [1,2,3,4,5] `Set.isProperSubsetOf` Set.fromList [1,2,3,4,5]  
False  
ghci> Set.fromList [2,3,4,8] `Set.isSubsetOf` Set.fromList [1,2,3,4,5]  
False
```

对集合也可以执行 `map` 和 `filter`:

```haskell
ghci> Set.filter odd $ Set.fromList [3,4,5,6,7,2,3,4]  
fromList [3,5,7]  
ghci> Set.map (+1) $ Set.fromList [3,4,5,6,7,2,3,4]  
fromList [3,4,5,6,7,8]
```

集合有一常见用途，那就是先 `fromList` 删掉重复元素后再 `toList` 转回去。尽管 `Data.List` 模块的 `nub` 函数完全可以完成这一工作，但在对付大 List 时则会明显的力不从心。使用集合则会快很多，`nub` 函数只需 List 中的元素属于 `Eq` 型别类就行了，而若要使用集合，它必须得属于 `Ord` 型别类

```haskell
ghci> let setNub xs = Set.toList $ Set.fromList xs  
ghci> setNub "HEY WHATS CRACKALACKIN"  
" ACEHIKLNRSTWY"  
ghci> nub "HEY WHATS CRACKALACKIN"  
"HEY WATSCRKLIN"
```

在处理较大的 List 时，`setNub` 要比 `nub` 快，但也可以从中看出，`nub` 保留了 List 中元素的原有顺序，而 `setNub` 不。

## 建立自己的模块

我们已经见识过了几个很酷的模块，但怎样才能构造自己的模块呢? 几乎所有的编程语言都允许你将代码分成多个文件，Haskell 也不例外。在编程时，将功能相近的函数和型别至于同一模块中会是个很好的习惯。这样一来，你就可以轻松地一个 `import` 来重用其中的函数.

接下来我们将构造一个由计算机几何图形体积和面积组成的模块，先从新建一个 `Geometry.hs` 的文件开始.

在模块的开头定义模块的名称，如果文件名叫做 `Geometry.hs` 那它的名字就得是 `Geometry`。在声明出它含有的函数名之后就可以编写函数的实现啦，就这样写:

```haskell
module Geometry  
( sphereVolume  
, sphereArea  
, cubeVolume  
, cubeArea  
, cuboidArea  
, cuboidVolume  
) where
```

如你所见，我们提供了对球体,立方体和立方体的面积和体积的解法。继续进发，定义函数体:

```haskell
module Geometry  
( sphereVolume  
, sphereArea  
, cubeVolume  
, cubeArea  
, cuboidArea  
, cuboidVolume  
) where  

sphereVolume :: Float -> Float  
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)  

sphereArea :: Float -> Float  
sphereArea radius = 4 * pi * (radius ^ 2)  

cubeVolume :: Float -> Float  
cubeVolume side = cuboidVolume side side side  

cubeArea :: Float -> Float  
cubeArea side = cuboidArea side side side  

cuboidVolume :: Float -> Float -> Float -> Float  
cuboidVolume a b c = rectangleArea a b * c  

cuboidArea :: Float -> Float -> Float -> Float  
cuboidArea a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2  

rectangleArea :: Float -> Float -> Float  
rectangleArea a b = a * b
```

![](/files/86MgcWm9NDrAoTNTLRQL)

标准的几何公式。有几个地方需要注意一下，由于立方体只是长方体的特殊形式，所以在求它面积和体积的时候我们就将它当作是边长相等的长方体。在这里还定义了一个 `helper`函数，`rectangleArea` 它可以通过长方体的两条边计算出长方体的面积。它仅仅是简单的相乘而已，份量不大。但请注意我们可以在这一模块中调用这个函数，而它不会被导出! 因为我们这个模块只与三维图形打交道.

当构造一个模块的时候，我们通常只会导出那些行为相近的函数，而其内部的实现则是隐蔽的。如果有人用到了 `Geometry` 模块，就不需要关心它的内部实现是如何。我们作为编写者，完全可以随意修改这些函数甚至将其删掉，没有人会注意到里面的变动，因为我们并不把它们导出.

要使用我们的模块，只需:

```haskell
import Geometry
```

将 `Geometry.hs` 文件至于用到它的进程文件的同一目录之下.

模块也可以按照分层的结构来组织，每个模块都可以含有多个子模块。而子模块还可以有自己的子模块。我们可以把 `Geometry` 分成三个子模块，而一个模块对应各自的图形对象.

首先，建立一个 `Geometry` 文件夹，注意首字母要大写，在里面新建三个文件

如下就是各个文件的内容:

Sphere.hs

```haskell
module Geometry.Sphere  
( volume  
, area  
) where  

volume :: Float -> Float  
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)  

area :: Float -> Float  
area radius = 4 * pi * (radius ^ 2)
```

Cuboid.hs

```haskell
module Geometry.Cuboid  
( volume  
, area  
) where  

volume :: Float -> Float -> Float -> Float  
volume a b c = rectangleArea a b * c  

area :: Float -> Float -> Float -> Float  
area a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2  

rectangleArea :: Float -> Float -> Float  
rectangleArea a b = a * b
```

Cube.hs

```haskell
module Geometry.Cube  
( volume  
, area  
) where  

import qualified Geometry.Cuboid as Cuboid  

volume :: Float -> Float  
volume side = Cuboid.volume side side side  

area :: Float -> Float  
area side = Cuboid.area side side side
```

好的! 先是 `Geometry.Sphere`。注意，我们将它置于 `Geometry` 文件夹之中并将它的名字定为 `Geometry.Sphere`。对 Cuboid 也是同样，也注意下，在三个模块中我们定义了许多名称相同的函数，因为所在模块不同，所以不会产生命名冲突。若要在 `Geometry.Cube` 使用 `Geometry.Cuboid` 中的函数，就不能直接 `import Geometry.Cuboid`，而必须得 `qualified import`。因为它们中间的函数名完全相同.

```haskell
import Geometry.Sphere
```

然后，调用 `area` 和 `volume`，就可以得到球体的面积和体积，而若要用到两个或更多此类模块，就必须得 `qualified import` 来避免重名。所以就得这样写:

```haskell
import qualified Geometry.Sphere as Sphere  
import qualified Geometry.Cuboid as Cuboid  
import qualified Geometry.Cube as Cube
```

然后就可以调用 `Sphere.area`，`Sphere.volume`，`Cuboid.area` 了，而每个函数都只计算其对应物体的面积和体积.

以后你若发现自己的代码体积庞大且函数众多，就应该试着找找目的相近的函数能否装入各自的模块，也方便日后的重用.


# ch08


# 构造我们自己的 Types 和 Typeclasses

## Algebraic Data Types 入门

在前面的章节中，我们谈了一些 Haskell 内置的型别和 Typeclass。而在本章中，我们将学习构造型别和 Typeclass 的方法。

我们已经见识过许多态别，如 `Bool`、`Int`、`Char`、`Maybe` 等等，不过在 Haskell 中该如何构造自己的型别呢？好问题，一种方法是使用 *data* 关键字。首先我们来看看 `Bool` 在标准函式库中的定义：

```haskell
data Bool = False | True
```

*data* 表示我们要定义一个新的型别。`=` 的左端标明型别的名称即 `Bool`，`=` 的右端就是\_值构造子\_ (*Value Constructor*)，它们明确了该型别可能的值。`|` 读作"或"，所以可以这样阅读该声明：`Bool` 型别的值可以是 `True` 或 `False`。型别名和值构造子的首字母必大写。

相似，我们可以假想 `Int` 型别的声明：

```haskell
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
```

![](/files/-Ma41uWVp2yw2ln7pvMf)

头尾两个值构造子分别表示了 `Int` 型别的最小值和最大值，注意到真正的型别宣告不是长这个样子的，这样写只是为了便于理解。我们用省略号表示中间省略的一大段数字。

我们想想 Haskell 中图形的表示方法。表示圆可以用一个 Tuple，如 `(43.1,55.0,10.4)`，前两项表示圆心的位置，末项表示半径。听着不错，不过三维矢量或其它什么东西也可能是这种形式！更好的方法就是自己构造一个表示图形的型别。假定图形可以是圆 (Circle) 或长方形 (Rectangle)：

```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
```

这是啥，想想？`Circle` 的值构造子有三个项，都是 Float。可见我们在定义值构造子时，可以在后面跟几个型别表示它包含值的型别。在这里，前两项表示圆心的坐标，尾项表示半径。`Rectangle` 的值构造子取四个 `Float` 项，前两项表示其左上角的坐标，后两项表示右下角的坐标。

谈到「项」 (field)，其实应为「参数」 (parameters)。值构造子的本质是个函数，可以返回一个型别的值。我们看下这两个值构造子的型别声明：

```haskell
ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape
ghci> :t Rectangle
Rectangle :: Float -> Float -> Float -> Float -> Shape
```

Cool，这么说值构造子就跟普通函数并无二致啰，谁想得到？我们写个函数计算图形面积：

```haskell
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

值得一提的是，它的型别声明表示了该函数取一个 `Shape` 值并返回一个 `Float` 值。写 `Circle -> Float` 是不可以的，因为 `Circle` 并非型别，真正的型别应该是 `Shape`。这与不能写`True->False` 的道理是一样的。再就是，我们使用的模式匹配针对的都是值构造子。之前我们匹配过 `[]`、`False` 或 `5`，它们都是不包含参数的值构造子。

我们只关心圆的半径，因此不需理会表示坐标的前两项：

```haskell
ghci> surface $ Circle 10 20 10
314.15927
ghci> surface $ Rectangle 0 0 100 100
10000.0
```

Yay，it works！不过我们若尝试输出 `Circle 10 20` 到控制台，就会得到一个错误。这是因为 Haskell 还不知道该型别的字符串表示方法。想想，当我们往控制台输出值的时候，Haskell 会先调用 `show` 函数得到这个值的字符串表示才会输出。因此要让我们的 `Shape` 型别成为 Show 型别类的成员。可以这样修改：

```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
```

先不去深究 *deriving*（派生），可以先这样理解：若在 `data` 声明的后面加上 `deriving (Show)`，那 Haskell 就会自动将该型别至于 `Show` 型别类之中。好了，由于值构造子是个函数，因此我们可以拿它交给 `map`，拿它不全调用，以及普通函数能做的一切。

```haskell
ghci> Circle 10 20 5
Circle 10.0 20.0 5.0
ghci> Rectangle 50 230 60 90
Rectangle 50.0 230.0 60.0 90.0
```

我们若要取一组不同半径的同心圆，可以这样：

```haskell
ghci> map (Circle 10 20) [4,5,6,6]
[Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]
```

我们的型别还可以更好。增加加一个表示二维空间中点的型别，可以让我们的 `Shape` 更加容易理解：

```haskell
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
```

注意下 `Point` 的定义，它的型别与值构造子用了相同的名字。没啥特殊含义，实际上，在一个型别含有唯一值构造子时这种重名是很常见的。好的，如今我们的 `Circle` 含有两个项，一个是 `Point` 型别，一个是 `Float` 型别，好作区分。`Rectangle` 也是同样，我们得修改 `surface` 函数以适应型别定义的变动。

```haskell
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

唯一需要修改的地方就是模式。在 `Circle` 的模式中，我们无视了整个 `Point`。而在 `Rectangle` 的模式中，我们用了一个嵌套的模式来取得 `Point` 中的项。若出于某原因而需要整个 `Point`，那么直接匹配就是了。

```haskell
ghci> surface (Rectangle (Point 0 0) (Point 100 100))
10000.0
ghci> surface (Circle (Point 0 0) 24)
1809.5574
```

表示移动一个图形的函数该怎么写？它应当取一个 `Shape` 和表示位移的两个数，返回一个位于新位置的图形。

```haskell
nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))
```

简洁明了。我们再给这一 `Shape` 的点加上位移的量。

```haskell
ghci> nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
```

如果不想直接处理 `Point`，我们可以搞个辅助函数 (auxilliary function)，初始从原点创建图形，再移动它们。

```haskell
baseCircle :: Float -> Shape
baseCircle r = Circle (Point 0 0) r

baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
```

```haskell
ghci> nudge (baseRect 40 100) 60 23
Rectangle (Point 60.0 23.0) (Point 100.0 123.0)
```

毫无疑问，你可以把你的数据型别导出到模块中。只要把你的型别与要导出的函数写到一起就是了。再在后面跟个括号，列出要导出的值构造子，用逗号隔开。如要导出所有的值构造子，那就写个..。

若要将这里定义的所有函数和型别都导出到一个模块中，可以这样：

```haskell
module Shapes
( Point(..)
, Shape(..)
, surface
, nudge
, baseCircle
, baseRect
) where
```

一个 `Shape` (..)，我们就导出了 `Shape` 的所有值构造子。这一来无论谁导入我们的模块，都可以用 `Rectangle` 和 `Circle` 值构造子来构造 `Shape` 了。这与写 `Shape(Rectangle,Circle)` 等价。

我们可以选择不导出任何 `Shape` 的值构造子，这一来使用我们模块的人就只能用辅助函数 `baseCircle` 和 `baseRect` 来得到 `Shape` 了。`Data.Map` 就是这一套，没有 `Map.Map [(1,2),(3,4)]`，因为它没有导出任何一个值构造子。但你可以用，像 `Map.fromList` 这样的辅助函数得到 `map`。应该记住，值构造子只是函数而已，如果不导出它们，就拒绝了使用我们模块的人调用它们。但可以使用其他返回该型别的函数，来取得这一型别的值。

不导出数据型别的值构造子隐藏了他们的内部实现，令型别的抽象度更高。同时，我们模块的用户也就无法使用该值构造子进行模式匹配了。

## Record Syntax

OK，我们需要一个数据型别来描述一个人，得包含他的姓、名、年龄、身高、电话号码以及最爱的冰淇淋。我不知你的想法，不过我觉得要了解一个人，这些数据就够了。就这样，实现出来！

```haskell
data Person = Person String String Int Float String String deriving (Show)
```

O\~Kay，第一项是名，第二项是姓，第三项是年龄，等等。我们造一个人：

```haskell
ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
ghci> guy
Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
```

貌似很酷，就是难读了点儿。弄个函数得人的某项数据又该如何？如姓的函数，名的函数，等等。好吧，我们只能这样：

```haskell
firstName :: Person -> String
firstName (Person firstname _ _ _ _ _) = firstname

lastName :: Person -> String
lastName (Person _ lastname _ _ _ _) = lastname

age :: Person -> Int
age (Person _ _ age _ _ _) = age

height :: Person -> Float
height (Person _ _ _ height _ _) = height

phoneNumber :: Person -> String
phoneNumber (Person _ _ _ _ number _) = number

flavor :: Person -> String
flavor (Person _ _ _ _ _ flavor) = flavor
```

唔，我可不愿写这样的代码！虽然 it works，但也太无聊了哇。

```haskell
ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
ghci> firstName guy
"Buddy"
ghci> height guy
184.2
ghci> flavor guy
"Chocolate"
```

你可能会说，一定有更好的方法！呃，抱歉，没有。

开个玩笑，其实有的，哈哈哈～Haskell 的发明者都是天才，早就料到了此类情形。他们引入了一个特殊的型别，也就是刚才提到的更好的方法 -- *Record Syntax*。

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     , height :: Float
                     , phoneNumber :: String
                     , flavor :: String
                     } deriving (Show)
```

与原先让那些项一个挨一个的空格隔开不同，这里用了花括号 `{}`。先写出项的名字，如 `firstName`，后跟两个冒号(也叫 Paamayim Nekudotayim，哈哈\~(译者不知道什么意思\~囧))，标明其型别，返回的数据型别仍与以前相同。这样的好处就是，可以用函数从中直接按项取值。通过 Record Syntax，Haskell 就自动生成了这些函数：`firstName`, `lastName`, `age`, `height`, `phoneNumber` 和 `flavor`。

```haskell
ghci> :t flavor
flavor :: Person -> String
ghci> :t firstName
firstName :: Person -> String
```

还有个好处，就是若派生 (deriving) 到 `Show` 型别类，它的显示是不同的。假如我们有个型别表示一辆车，要包含生产商、型号以及出场年份：

```haskell
data Car = Car String String Int deriving (Show)
```

```haskell
ghci> Car "Ford" "Mustang" 1967
Car "Ford" "Mustang" 1967
```

若用 Record Syntax，就可以得到像这样的新车：

```haskell
data Car = Car {company :: String, model :: String, year :: Int} deriving (Show)
```

```haskell
ghci> Car {company="Ford", model="Mustang", year=1967}
Car {company = "Ford", model = "Mustang", year = 1967}
```

这一来在造车时我们就不必关心各项的顺序了。

表示三维矢量之类简单数据，`Vector = Vector Int Int Int` 就足够明白了。但一个值构造子中若含有很多个项且不易区分，如一个人或者一辆车啥的，就应该使用 Record Syntax。

## Type parameters

值构造子可以取几个参数产生一个新值，如 `Car` 的构造子是取三个参数返回一个 `Car`。与之相似，型别构造子可以取型别作参数，产生新的型别。这咋一听貌似有点深奥，不过实际上并不复杂。如果你对 C++ 的模板有了解，就会看到很多相似的地方。我们看一个熟悉的型别，好对型别参数有个大致印象：

```haskell
data Maybe a = Nothing | Just a
```

![](/files/-Ma41uWa0k2x8FJVO3EN)

这里的a就是个型别参数。也正因为有了它，`Maybe` 就成为了一个型别构造子。在它的值不是 `Nothing` 时，它的型别构造子可以搞出 `Maybe Int`，`Maybe String` 等等诸多态别。但只一个 `Maybe` 是不行的，因为它不是型别，而是型别构造子。要成为真正的型别，必须得把它需要的型别参数全部填满。

所以，如果拿 `Char` 作参数交给 `Maybe`，就可以得到一个 `Maybe Char` 的型别。如，`Just 'a'` 的型别就是 `Maybe Char` 。

你可能并未察觉，在遇见 Maybe 之前我们早就接触到型别参数了。它便是 List 型别。这里面有点语法糖，List 型别实际上就是取一个参数来生成一个特定型别，这型别可以是 `[Int]`，`[Char]` 也可以是 `[String]`，但不会跟在 `[]` 的后面。

把玩一下 `Maybe`！

```haskell
ghci> Just "Haha"
Just "Haha"
ghci> Just 84
Just 84
ghci> :t Just "Haha"
Just "Haha" :: Maybe [Char]
ghci> :t Just 84
Just 84 :: (Num t) => Maybe t
ghci> :t Nothing
Nothing :: Maybe a
ghci> Just 10 :: Maybe Double
Just 10.0
```

型别参数很实用。有了它，我们就可以按照我们的需要构造出不同的型别。若执行 `:t Just "Haha"`，型别推导引擎就会认出它是个 `Maybe [Char]`，由于 `Just a` 里的 `a` 是个字符串，那么 `Maybe a` 里的 `a` 一定也是个字符串。

![](/files/-Ma41uWdDcmX88diPjV-)

注意下，`Nothing` 的型别为 `Maybe a`。它是多态的，若有函数取 `Maybe Int` 型别的参数，就一概可以传给它一个 `Nothing`，因为 `Nothing` 中不包含任何值。`Maybe a` 型别可以有 `Maybe Int` 的行为，正如 `5` 可以是 `Int` 也可以是 `Double`。与之相似，空 List 的型别是 `[a]`，可以与一切 List 打交道。因此，我们可以 `[1,2,3]++[]`，也可以 `["ha","ha,","ha"]++[]`。

型别参数有很多好处，但前提是用对了地方才行。一般都是不关心型别里面的内容，如 `Maybe a`。一个型别的行为若有点像是容器，那么使用型别参数会是个不错的选择。我们完全可以把我们的`Car`型别从

```haskell
data Car = Car { company :: String
                 , model :: String
                 , year :: Int
                 } deriving (Show)
```

改成：

```haskell
data Car a b c = Car { company :: a
                       , model :: b
                       , year :: c
                        } deriving (Show)
```

但是，这样我们又得到了什么好处？回答很可能是，一无所得。因为我们只定义了处理 `Car String String Int` 型别的函数，像以前，我们还可以弄个简单函数来描述车的属性。

```haskell
tellCar :: Car -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
```

```haskell
ghci> let stang = Car {company="Ford", model="Mustang", year=1967}
ghci> tellCar stang
"This Ford Mustang was made in 1967"
```

可爱的小函数！它的型别声明得很漂亮，而且工作良好。好，如果改成 `Car a b c` 又会怎样？

```haskell
tellCar :: (Show a) => Car String String a -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
```

我们只能强制性地给这个函数安一个 `(Show a) => Car String String a` 的型别约束。看得出来，这要繁复得多。而唯一的好处貌似就是，我们可以使用 `Show` 型别类的 `instance` 来作 `a` 的型别。

```haskell
ghci> tellCar (Car "Ford" "Mustang" 1967)
"This Ford Mustang was made in 1967"
ghci> tellCar (Car "Ford" "Mustang" "nineteen sixty seven")
"This Ford Mustang was made in \"nineteen sixty seven\""
ghci> :t Car "Ford" "Mustang" 1967
Car "Ford" "Mustang" 1967 :: (Num t) => Car [Char] [Char] t
ghci> :t Car "Ford" "Mustang" "nineteen sixty seven"
Car "Ford" "Mustang" "nineteen sixty seven" :: Car [Char] [Char] [Char]
```

其实在现实生活中，使用 `Car String String Int` 在大多数情况下已经满够了。所以给 `Car` 型别加型别参数貌似并没有什么必要。通常我们都是都是在一个型别中包含的型别并不影响它的行为时才引入型别参数。一组什么东西组成的 List 就是一个 List，它不关心里面东西的型别是啥，然而总是工作良好。若取一组数字的和，我们可以在后面的函数体中明确是一组数字的 List。Maybe 与之相似，它表示可以有什么东西可以没有，而不必关心这东西是啥。

我们之前还遇见过一个型别参数的应用，就是 `Data.Map` 中的 `Map k v`。 `k` 表示 Map 中键的型别，`v` 表示值的型别。这是个好例子，Map 中型别参数的使用允许我们能够用一个型别索引另一个型别，只要键的型别在 `Ord` 型别类就行。如果叫我们自己定义一个 Map 型别，可以在 `data` 声明中加上一个型别类的约束。

```haskell
data (Ord k) => Map k v = ...
```

然而 Haskell 中有一个严格的约定，那就是永远不要在 `data` 声明中添加型别约束。为啥？嗯，因为这样没好处，反而得写更多不必要的型别约束。`Map k v` 要是有 `Ord k` 的约束，那就相当于假定每个 Map 的相关函数都认为 `k` 是可排序的。若不给数据型别加约束，我们就不必给那些不关心键是否可排序的函数另加约束了。这类函数的一个例子就是 `toList`，它只是把一个 Map 转换为关联 List 罢了，型别声明为 `toList :: Map k v -> [(k, v)]`。要是加上型别约束，就只能是 `toList :: (Ord k) =>Map k a -> [(k,v)]`，明显没必要嘛。

所以说，永远不要在 `data` 声明中加型别约束 --- 即便看起来没问题。免得在函数声明中写出过多无谓的型别约束。

我们实现个表示三维矢量的型别，再给它加几个处理函数。我么那就给它个型别参数，虽然大多数情况都是数值型，不过这一来它就支持了多种数值型别。

```haskell
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
vectMult :: (Num t) => Vector t -> t -> Vector t
(Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
scalarMult :: (Num t) => Vector t -> Vector t -> t
(Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
```

`vplus` 用来相加两个矢量，即将其所有对应的项相加。`scalarMult` 用来求两个矢量的标量积，`vectMult` 求一个矢量和一个标量的积。这些函数可以处理 `Vector Int`，`Vector Integer`，`Vector Float` 等等型别，只要 `Vector a` 里的这个 `a` 在 `Num` 型别类中就行。同样，如果你看下这些函数的型别声明就会发现，它们只能处理相同型别的矢量，其中包含的数字型别必须与另一个矢量一致。注意，我们并没有在 `data` 声明中添加 `Num` 的类约束。反正无论怎么着都是给函数加约束。

再度重申，型别构造子和值构造子的区分是相当重要的。在声明数据型别时，等号=左端的那个是型别构造子，右端的(中间可能有|分隔)都是值构造子。拿 `Vector t t t -> Vector t t t -> t` 作函数的型别就会产生一个错误，因为在型别声明中只能写型别，而 `Vector` 的型别构造子只有个参数，它的值构造子才是有三个。我们就慢慢耍：

```haskell
ghci> Vector 3 5 8 `vplus` Vector 9 2 8
Vector 12 7 16
ghci> Vector 3 5 8 `vplus` Vector 9 2 8 `vplus` Vector 0 2 3
Vector 12 9 19
ghci> Vector 3 9 7 `vectMult` 10
Vector 30 90 70
ghci> Vector 4 9 5 `scalarMult` Vector 9.0 2.0 4.0
74.0
ghci> Vector 2 9 3 `vectMult` (Vector 4 9 5 `scalarMult` Vector 9 2 4)
Vector 148 666 222
```

## Derived instances

![](/files/OiE3GZUyVwTW7KbW3Dgm)

在 [Typeclass 101](https://github.com/MnO2/learnyouahaskell-zh/blob/master/zh-cn/ch03/type-and-typeclass/README.md#typeclasses-ru-men) 那一节里面，我们了解了 Typeclass 的基础内容。里面提到，型别类就是定义了某些行为的接口。例如，Int 型别是 `Eq` 型别类的一个 instance，`Eq` 类就定义了判定相等性的行为。Int 值可以判断相等性，所以 Int 就是 `Eq` 型别类的成员。它的真正威力体现在作为 `Eq` 接口的函数中，即 `==` 和 `/=`。只要一个型别是 `Eq` 型别类的成员，我们就可以使用 `==` 函数来处理这一型别。这便是为何 `4==4` 和 `"foo"/="bar"` 这样的表达式都需要作型别检查。

我们也曾提到，人们很容易把型别类与 Java，Python，C++ 等语言的类混淆。很多人对此都倍感不解，在原先那些语言中，类就像是蓝图，我们可以根据它来创造对象、保存状态并执行操作。而型别类更像是接口，我们不是靠它构造数据，而是给既有的数据型别描述行为。什么东西若可以判定相等性，我们就可以让它成为 `Eq` 型别类的 instance。什么东西若可以比较大小，那就可以让它成为 `Ord` 型别类的 instance。

在下一节，我们将看一下如何手工实现型别类中定义函数来构造 instance。现在呢，我们先了解下 Haskell 是如何自动生成这几个型别类的 instance，`Eq`, `Ord`, `Enum`, `Bounded`, `Show`, `Read`。只要我们在构造型别时在后面加个 `deriving`(派生)关键字，Haskell 就可以自动地给我们的型别加上这些行为。

看这个数据型别：

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     }
```

这描述了一个人。我们先假定世界上没有重名重姓又同龄的人存在，好，假如有两个 record，有没有可能是描述同一个人呢？当然可能，我么可以判定姓名年龄的相等性，来判断它俩是否相等。这一来，让这个型别成为 `Eq` 的成员就很靠谱了。直接 derive 这个 instance：

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq)
```

在一个型别 derive 为 `Eq` 的 instance 后，就可以直接使用 `==` 或 `/=` 来判断它们的相等性了。Haskell 会先看下这两个值的值构造子是否一致(这里只是单值构造子)，再用 `==` 来检查其中的所有数据(必须都是 `Eq` 的成员)是否一致。在这里只有 `String` 和 Int，所以是没有问题的。测试下我们的 Eqinstance：

```haskell
ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}
ghci> let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}
ghci> mca == adRock
False
ghci> mikeD == adRock
False
ghci> mikeD == mikeD
True
ghci> mikeD == Person {firstName = "Michael", lastName = "Diamond", age = 43}
True
```

自然，`Person` 如今已经成为了 `Eq` 的成员，我们就可以将其应用于所有在型别声明中用到 `Eq` 类约束的函数了，如 `elem`。

```haskell
ghci> let beastieBoys = [mca, adRock, mikeD]
ghci> mikeD `elem` beastieBoys
True
```

`Show` 和 `Read` 型别类处理可与字符串相互转换的东西。同 `Eq` 相似，如果一个型别的构造子含有参数，那所有参数的型别必须都得属于 `Show` 或 `Read` 才能让该型别成为其 instance。就让我们的 `Person` 也成为 `Read` 和 `Show` 的一员吧。

```haskell
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq, Show, Read)
```

然后就可以输出一个 `Person` 到控制台了。

```haskell
ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> mikeD
Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> "mikeD is: " ++ show mikeD
"mikeD is: Person {firstName = \"Michael\", lastName = \"Diamond\", age = 43}"
```

如果我们还没让 `Person` 型别作为 `Show` 的成员就尝试输出它，Haskell 就会向我们抱怨，说它不知道该怎么把它表示成一个字符串。不过现在既然已经 derive 成为了 `Show` 的一个 instance，它就知道了。

`Read` 几乎就是与 `Show` 相对的型别类，`show` 是将一个值转换成字符串，而 `read` 则是将一个字符串转成某型别的值。还记得，使用 `read` 函数时我们必须得用型别注释注明想要的型别，否则 Haskell 就不会知道如何转换。

```haskell
ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" :: Person
Person {firstName = "Michael", lastName = "Diamond", age = 43}
```

如果我们 `read` 的结果会在后面用到参与计算，Haskell 就可以推导出是一个 Person 的行为，不加注释也是可以的。

```haskell
ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" == mikeD
True
```

也可以 `read` 带参数的型别，但必须填满所有的参数。因此 `read "Just 't'" :: Maybe a` 是不可以的，`read "Just 't'" :: Maybe Char` 才对。

很容易想象 `Ord` 型别类 derive instance 的行为。首先，判断两个值构造子是否一致，如果是，再判断它们的参数，前提是它们的参数都得是 `Ord` 的 instance。`Bool` 型别可以有两种值，`False` 和 `True`。为了了解在比较中进程的行为，我们可以这样想象：

```haskell
data Bool = False | True deriving (Ord)
```

由于值构造子 `False` 安排在 `True` 的前面，我们可以认为 `True` 比 `False` 大。

```haskell
ghci> True `compare` False
GT
ghci> True > False
True
ghci> True < False
False
```

在 `Maybe a` 数据型别中，值构造子 `Nothing` 在 `Just` 值构造子前面，所以一个 `Nothing` 总要比 `Just something` 的值小。即便这个 `something` 是 `-100000000` 也是如此。

```haskell
ghci> Nothing < Just 100
True
ghci> Nothing > Just (-49999)
False
ghci> Just 3 `compare` Just 2
GT
ghci> Just 100 > Just 50
True
```

不过类似 `Just (*3) > Just (*2)` 之类的代码是不可以的。因为 `(*3)` 和 `(*2)` 都是函数，而函数不是 `Ord` 类的成员。

作枚举，使用数字型别就能轻易做到。不过使用 `Enum` 和 `Bounded` 型别类会更好，看下这个型别：

```haskell
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
```

所有的值构造子都是 `nullary` 的(也就是没有参数)，每个东西都有前置子和后继子，我们可以让它成为 `Enum` 型别类的成员。同样，每个东西都有可能的最小值和最大值，我们也可以让它成为 `Bounded` 型别类的成员。在这里，我们就同时将它搞成其它可 derive型别类的 instance。再看看我们能拿它做啥：

```haskell
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
           deriving (Eq, Ord, Show, Read, Bounded, Enum)
```

由于它是 `Show` 和 `Read` 型别类的成员，我们可以将这个型别的值与字符串相互转换。

```haskell
ghci> Wednesday
Wednesday
ghci> show Wednesday
"Wednesday"
ghci> read "Saturday" :: Day
Saturday
```

由于它是 `Eq` 与 `Ord` 的成员，因此我们可以拿 `Day` 作比较。

```haskell
ghci> Saturday == Sunday
False
ghci> Saturday == Saturday
True
ghci> Saturday > Friday
True
ghci> Monday `compare` Wednesday
LT
```

它也是 `Bounded` 的成员，因此有最早和最晚的一天。

```haskell
ghci> minBound :: Day
Monday
ghci> maxBound :: Day
Sunday
```

它也是 `Enum` 的 instance，可以得到前一天和后一天，并且可以对此使用 List 的区间。

```haskell
ghci> succ Monday
Tuesday
ghci> pred Saturday
Friday
ghci> [Thursday .. Sunday]
[Thursday,Friday,Saturday,Sunday]
ghci> [minBound .. maxBound] :: [Day]
[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]
```

那是相当的棒。

## Type synonyms

在前面我们提到在写型别名的时候，`[Char]` 和 `String` 等价，可以互换。这就是由型别别名实现的。型别别名实际上什么也没做，只是给型别提供了不同的名字，让我们的代码更容易理解。这就是 `[Char]` 的别名 `String` 的由来。

```haskell
type String = [Char]
```

我们已经介绍过了 `type` 关键字，这个关键字有一定误导性，它并不是用来创造新类（这是 `data` 关键字做的事情），而是给一个既有型别提供一个别名。

如果我们随便搞个函数 `toUpperString` 或其他什么名字，将一个字符串变成大写，可以用这样的型别声明 `toUpperString :: [Char] -> [Char]`， 也可以这样 `toUpperString :: String -> String`，二者在本质上是完全相同的。后者要更易读些。

在前面 `Data.Map` 那部分，我们用了一个关联 `List` 来表示 `phoneBook`，之后才改成的 Map。我们已经发现了，一个关联 List 就是一组键值对组成的 List。再看下我们 `phoneBook` 的样子：

```haskell
phoneBook :: [(String,String)]
phoneBook =
    [("betty","555-2938")
    ,("bonnie","452-2928")
    ,("patsy","493-2928")
    ,("lucille","205-2928")
    ,("wendy","939-8282")
    ,("penny","853-2492")
    ]
```

可以看出，`phoneBook` 的型别就是 `[(String,String)]`，这表示一个关联 List 仅是 String 到 String 的映射关系。我们就弄个型别别名，好让它型别声明中能够表达更多信息。

```haskell
type PhoneBook = [(String,String)]
```

现在我们 `phoneBook` 的型别声明就可以是 `phoneBook :: PhoneBook` 了。再给字符串加上别名：

```haskell
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
```

Haskell 程序员们给 String 加别名是为了让函数中字符串的表达方式及用途更加明确。

好的，我们实现了一个函数，它可以取一名字和号码检查它是否存在于电话本。现在可以给它加一个相当好看明了的型别声明：

```haskell
inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool
inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook
```

![](/files/OLQ73XIj22Ri3Td3ArVN)

如果不用型别别名，我们函数的型别声明就只能是 `String -> String -> [(String ,String)] -> Bool` 了。在这里使用型别别名是为了让型别声明更加易读，但你也不必拘泥于它。引入型别别名的动机既非单纯表示我们函数中的既有型别，也不是为了替换掉那些重复率高的长名字体别(如 `[(String,String)]`)，而是为了让型别对事物的描述更加明确。

型别别名也是可以有参数的，如果你想搞个型别来表示关联 List，但依然要它保持通用，好让它可以使用任意型别作 `key` 和 `value`，我们可以这样：

```haskell
type AssocList k v = [(k,v)]
```

好的，现在一个从关联 List 中按键索值的函数型别可以定义为 `(Eq k) => k -> AssocList k v -> Maybe`。`AssocList` 是个取两个型别做参数生成一个具体型别的型别构造子，如 `Assoc Int String` 等等。

```
*Fronzie 说*：Hey！当我提到具体型别，那我就是说它是完全调用的，就像 ``Map Int String``。要不就是多态函数中的 ``[a]`` 或 ``(Ord a) => Maybe a`` 之类。有时我和孩子们会说 "Maybe 型别"，但我们的意思并不是按字面来，傻瓜都知道 ``Maybe`` 是型别构造子嘛。只要用一个明确的型别调用 ``Maybe``，如 ``Maybe String`` 可得一个具体型别。你知道，只有具体型别才可以保存值。
```

我们可以用不全调用来得到新的函数，同样也可以使用不全调用得到新的型别构造子。同函数一样，用不全的型别参数调用型别构造子就可以得到一个不全调用的型别构造子，如果我们要一个表示从整数到某东西间映射关系的型别，我们可以这样：

```haskell
type IntMap v = Map Int v
```

也可以这样：

```haskell
type IntMap = Map Int
```

无论怎样，`IntMap` 的型别构造子都是取一个参数，而它就是这整数指向的型别。

Oh yeah，如果要你去实现它，很可能会用个 `qualified import` 来导入 `Data.Map`。这时，型别构造子前面必须得加上模块名。所以应该写个 `type IntMap = Map.Map Int`

你得保证真正弄明白了型别构造子和值构造子的区别。我们有了个叫 `IntMap` 或者 `AssocList` 的别名并不意味着我们可以执行类似 `AssocList [(1,2),(4,5),(7,9)]` 的代码，而是可以用不同的名字来表示原先的 List，就像 `[(1,2),(4,5),(7,9)] :: AssocList Int Int` 让它里面的型别都是 Int。而像处理普通的 Tuple 构成的那种 List 处理它也是可以的。型别别名(型别依然不变)，只可以在 Haskell 的型别部分中使用，像定义新型别或型别声明或型别注释中跟在::后面的部分。

另一个很酷的二参型别就是 `Either a b` 了，它大约是这样定义的：

```haskell
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
```

它有两个值构造子。如果用了 `Left`，那它内容的型别就是 `a`；用了 `Right`，那它内容的型别就是 `b`。我们可以用它来将可能是两种型别的值封装起来，从里面取值时就同时提供 `Left` 和 `Right` 的模式匹配。

```haskell
ghci> Right 20
Right 20
ghci> Left "w00t"
Left "w00t"
ghci> :t Right 'a'
Right 'a' :: Either a Char
ghci> :t Left True
Left True :: Either Bool b
```

到现在为止，`Maybe` 是最常见的表示可能失败的计算的型别了。但有时 `Maybe` 也并不是十分的好用，因为 `Nothing` 中包含的信息还是太少。要是我们不关心函数失败的原因，它还是不错的。就像 `Data.Map` 的 `lookup` 只有在搜索的项不在 Map 时才会失败，对此我们一清二楚。但我们若想知道函数失败的原因，那还得使用 `Either a b`，用 `a` 来表示可能的错误的型别，用 `b` 来表示一个成功运算的型别。从现在开始，错误一律用 `Left` 值构造子，而结果一律用 `Right`。

一个例子：有个学校提供了不少壁橱，好给学生们地方放他们的 Gun'N'Rose 海报。每个壁橱都有个密码，哪个学生想用个壁橱，就告诉管理员壁橱的号码，管理员就会告诉他壁橱的密码。但如果这个壁橱已经让别人用了，管理员就不能告诉他密码了，得换一个壁橱。我们就用 `Data.Map` 的一个 Map 来表示这些壁橱，把一个号码映射到一个表示壁橱占用情况及密码的 Tuple 里。

```haskell
import qualified Data.Map as Map

data LockerState = Taken | Free deriving (Show, Eq)

type Code = String

type LockerMap = Map.Map Int (LockerState, Code)
```

很简单，我们引入了一个新的型别来表示壁橱的占用情况。并为壁橱密码及按号码找壁橱的 Map 分别设置了一个别名。好，现在我们实现这个按号码找壁橱的函数，就用 `Either String Code` 型别表示我们的结果，因为 `lookup` 可能会以两种原因失败。橱子已经让别人用了或者压根就没有这个橱子。如果 `lookup` 失败，就用字符串表明失败的原因。

```haskell
lockerLookup :: Int -> LockerMap -> Either String Code
lockerLookup lockerNumber map =
    case Map.lookup lockerNumber map of
        Nothing -> Left $ "Locker number " ++ show lockerNumber ++ " doesn't exist!"
        Just (state, code) -> if state /= Taken
                                then Right code
                                else Left $ "Locker " ++ show lockerNumber ++ " is already taken!"
```

我们在这里个 Map 中执行一次普通的 `lookup`，如果得到一个 `Nothing`，就返回一个 `Left String` 的值，告诉他压根就没这个号码的橱子。如果找到了，就再检查下，看这橱子是不是已经让别人用了，如果是，就返回个 `Left String` 说它已经让别人用了。否则就返回个 `Right Code` 的值，通过它来告诉学生壁橱的密码。它实际上就是个 `Right String`，我们引入了个型别别名让它这型别声明更好看。

如下是个 Map 的例子：

```haskell
lockers :: LockerMap
lockers = Map.fromList
    [(100,(Taken,"ZD39I"))
    ,(101,(Free,"JAH3I"))
    ,(103,(Free,"IQSA9"))
    ,(105,(Free,"QOTSA"))
    ,(109,(Taken,"893JJ"))
    ,(110,(Taken,"99292"))
    ]
```

现在从里面 `lookup` 某个橱子号..

```haskell
ghci> lockerLookup 101 lockers
Right "JAH3I"
ghci> lockerLookup 100 lockers
Left "Locker 100 is already taken!"
ghci> lockerLookup 102 lockers
Left "Locker number 102 doesn't exist!"
ghci> lockerLookup 110 lockers
Left "Locker 110 is already taken!"
ghci> lockerLookup 105 lockers
Right "QOTSA"
```

我们完全可以用 `Maybe a` 来表示它的结果，但这样一来我们就对得不到密码的原因不得而知了。而在这里，我们的新型别可以告诉我们失败的原因。

## Recursive data structures (递归地定义数据结构)

如我们先前看到的，一个 algebraic data type 的构造子可以有好几个 field，其中每个 field 都必须有具体的型态。有了那个概念，我们能定义一个型态，其中他的构造子的 field 的型态是他自己。这样我们可以递归地定义下去，某个型态的值便可能包含同样型态的值，进一步下去他还可以再包含同样型态的值。

考虑一下 List: `[5]`。他其实是 `5:[]` 的语法糖。在 `:` 的左边是一个普通值，而在右边是一串 List。只是在这个案例中是空的 List。再考虑 `[4,5]`。他可以看作 `4:(5:[])`。看看第一个 `:`，我们看到他也有一个元素在左边，一串 List `5:[]` 在右边。同样的道理 `3:(4:(5:6:[]))` 也是这样。

我们可以说一个 List 的定义是要么是空的 List 或是一个元素，后面用 `:` 接了另一串 List。

我们用 algebraic data type 来实作我们自己的 List！

```haskell
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
```

这读起来好像我们前一段提及的定义。他要么是空的 List，或是一个元素跟一串 List 的结合。如果你被搞混了，看看用 record syntax 定义的可能比较清楚。

```haskell
data List a = Empty | Cons { listHead :: a, listTail :: List a} deriving (Show, Read, Eq, Ord)
```

你可能也对这边的 `Cons` 构造子不太清楚。`cons` 其实就是指 `:`。对 List 而言，`:` 其实是一个构造子，他接受一个值跟另一串 List 来构造一个 List。现在我们可以使用我们新定义的 List 型态。换句话说，他有两个 field，其中一个 field 具有型态 `a`，另一个有型态 `[a]`。

```haskell
ghci> Empty
Empty
ghci> 5 `Cons` Empty
Cons 5 Empty
ghci> 4 `Cons` (5 `Cons` Empty)
Cons 4 (Cons 5 Empty)
ghci> 3 `Cons` (4 `Cons` (5 `Cons` Empty))
Cons 3 (Cons 4 (Cons 5 Empty))
```

我们用中缀的方式调用 `Cons` 构造子，这样你可以很清楚地看到他就是 `:`。`Empty` 代表 `[]`，而 ``4 `Cons` (5 `Cons` Empty)`` 就是 `4:(5:[])`。

我们可以只用特殊字符来定义函数，这样他们就会自动具有中缀的性质。我们也能同样的手法套用在构造子上，毕竟他们不过是回传型态的函数而已。

```haskell
infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
```

首先我们留意新的语法结构：fixity 宣告。当我们定义函数成 operator，我们能同时指定 fixity (但并不是必须的)。fixity 指定了他应该是 left-associative 或是 right-associative，还有他的优先级。例如说，`*` 的 fixity 是 `infixl 7 *`，而 `+` 的 fixity 是 `infixl 6`。代表他们都是 left-associative。`(4 * 3 * 2)` 等于 `((4 * 3) * 2)`。但 `*` 拥有比 `+` 更高的优先级。所以 `5 * 4 + 3` 会是 `(5 * 4) + 3`。

这样我们就可以写成 `a :-: (List a)` 而不是 `Cons a (List a)`：

```haskell
ghci> 3 :-: 4 :-: 5 :-: Empty
(:-:) 3 ((:-:) 4 ((:-:) 5 Empty))
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> 100 :-: a
(:-:) 100 ((:-:) 3 ((:-:) 4 ((:-:) 5 Empty)))
```

Haskell 在宣告 `deriving Show` 的时候，他会仍视构造子为前缀函数，因此必须要用括号括起来。

我们在来写个函数来把两个 List 连起来。一般 `++` 在操作普通 List 的时候是这样的：

```haskell
infixr 5  ++
(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
```

我们把他偷过来用在我们的 List 上，把函数命名成 `.++`：

```haskell
infixr 5  .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
(x :-: xs) .++ ys = x :-: (xs .++ ys)
```

来看看他如何运作：

```haskell
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> let b = 6 :-: 7 :-: Empty
ghci> a .++ b
(:-:) 3 ((:-:) 4 ((:-:) 5 ((:-:) 6 ((:-:) 7 Empty))))
```

如果我们想要的话，我们可以定义其他操作我们list的函数。

注意到我们是如何利用 `(x :-: xs)` 做模式匹配的。他运作的原理实际上就是利用到构造子。我们可以利用 `:-:` 做模式匹配原因就是他是构造子，同样的 `:` 也是构造子所以可以用他做匹配。`[]` 也是同样道理。由于模式匹配是用构造子来作的，所以我们才能对像 `8`, `'a'` 之类的做模式匹配。他们是数值与字符的构造子。

接下来我们要实作二元搜索树 (binary search tree)。如果你对二元搜索树不太清楚，我们来快速地解释一遍。他的结构是每个节点指向两个其他节点，一个在左边一个在右边。在左边节点的元素会比这个节点的元素要小。在右边的话则比较大。每个节点最多可以有两棵子树。而我们知道譬如说一棵包含 5 的节点的左子树，里面所有的元素都会小于 5。而节点的右子树里面的元素都会大于 5。如果我们想找找看 8是不是在我们的树里面，我们就从 5 那个节点找起，由于 8 比 5 要大，很自然地就会往右搜索。接着我们走到 7，又由于 8 比 7 要大，所以我们再往右走。我们在三步就找到了我们要的元素。如果这不是棵树而是 List 的话，那就会需要花到七步才能找到 8。

`Data.Set` 跟 `Data.Map` 中的 `set` 和 Map 都是用树来实现的，只是他们是用平衡二元搜索树而不是随意的二元搜索树。不过这边我们就只先写一棵普通的二元搜索树就好了。

这边我们来定义一棵树的结构：他不是一棵空的树就是带有值并含有两棵子树。听起来非常符合 algebraic data type 的结构！

```haskell
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
```

我们不太想手动来建棵二元搜索树，所以我们要来写一个函数，他接受一棵树还有一个元素，把这个元素安插到这棵二元搜索树中。当拿这个元素跟树的节点比较结果比较小的话，我们就往左走，如果比较大，就往右走。重复这个动作直到我们走到一棵空的树。一旦碰到空的树的话，我们就把元素插入节点。

在 C 语言中，我们是用修改指标的方式来达成这件事。但在 Haskell 中，我们没办法修改我们的树。所以我们在决定要往左或往右走的时候就做一棵新的子树，走到最后要安插节点的时候也是做一棵新的树。因此我们插入函数的型态会是 `a -> Tree a -> Tree a`。他接受一个元素跟一棵树，并回传一棵包含了新元素的新的树。这看起来很没效率的样子，但别担心，惰性的特性可以让我们不用担心这个。

来看下列两个函数。第一个做了一个单节点的树，而第二个插入一个元素到一棵树中。

```haskell
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
      | x == a = Node x left right
      | x < a  = Node a (treeInsert x left) right
      | x > a  = Node a left (treeInsert x right)
```

`singleton` 函数只是一个做一个含有两棵空子树的节点的函数的别名。在插入的操作中，我们先为终端条件定义了一个模式匹配。如果我们走到了一棵空的子树，这表示我们到达了我们想要的地方，我们便建造一棵空的单元素的树来放在那个位置。如果我们还没走到一棵空的树来插入我们的元素。那就必须要做一些检查来往下走。如果我们要安插的元素跟 root 所含有的元素相等，那就直接回传这棵树。如果安插的元素比较小，就回传一棵新的树。这棵树的 root 跟原来的相同，右子树也相同，只差在我们要安插新的元素到左子树中。如果安插的元素反而比较大，那整个过程就相反。

接下来，我们要写一个函数来检查某个元素是否已经在这棵树中。首先我们定义终端条件。如果我们已经走到一棵空的树，那这个元素一定不在这棵树中。这跟我们搜索 List 的情形是一致的。如果我们要在空的 List 中搜索某一元素，那就代表他不在这个 List 里面。假设我们现在搜索一棵非空的树，而且 root 中的元素刚好就是我们要的，那就找到了。那如果不是呢？我们就要利用在 root 节点左边的元素都比 root 小的这个性质。如果我们的元素比 root 小，那就往左子树中找。如果比较大，那就往右子树中找。

```haskell
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
    | x == a = True
    | x < a  = treeElem x left
    | x > a  = treeElem x right
```

我们要作的就是把之前段落所描述的事转换成代码。首先我们不想手动一个个来创造一棵树。我们想用一个 `fold` 来从一个 List 创造一棵树。要知道走遍一个 List 并回传某种值的操作都可以用 `fold` 来实现。我们先从一棵空的树开始，然后从右边走过 List的 每一个元素，一个一个丢到树里面。

```haskell
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))
```

在 `foldr` 中，`treeInsert` 是做 folding 操作的函数，而 `EmptyTree` 是起始的 accumulator，`nums` 则是要被走遍的 List。

当我们想把我们的树印出来的时候，印出来的形式会不太容易读。但如果我们能有结构地印出来呢？我们知道 root 是 5，他有两棵子树，其中一个的 root 是 3 另一个则是 7。

```haskell
ghci> 8 `treeElem` numsTree
True
ghci> 100 `treeElem` numsTree
False
ghci> 1 `treeElem` numsTree
True
ghci> 10 `treeElem` numsTree
False
```

检查元素是否属于某棵树的函数现在能正常运作了！

你可以看到 algebraic data structures 是非常有力的概念。我们可以使用这个结构来构造出布尔值，周一到周五的概念，甚至还有二元树。

## Typeclasses 的第二堂课

到目前为止我们学到了一些 Haskell 中的标准 typeclass，也学到了某些已经定义为他们 instance 的型别。我们知道如何让我们自己定义的型别自动被 Haskell 所推导成标准 typeclass 的 instance。在这个章节中，我们会学到如何构造我们自己的 typeclass，并且如何构造这些 typeclass 的 type instance。

来快速复习一下什么是 typeclass: typeclass 就像是 interface。一个 typeclass 定义了一些行为(像是比较相不相等，比较大小顺序，能否穷举)而我们会把希望满足这些性质的型别定义成这些 typeclass 的 instance。typeclass 的行为是由定义的函数来描述。并写出对应的实作。当我们把一个型别定义成某个 typeclass 的 instance，就表示我们可以对那个型别使用 typeclass 中定义的函数。

Typeclass 跟 Java 或 Python 中的 class 一点关系也没有。这个概念让很多人混淆，所以我希望你先忘掉所有在命令式语言中学到有关 class 的所有东西。

例如说，`Eq` 这个 typeclass 是描述可以比较相等的事物。他定义了 `==` 跟 `/=` 两个函数。如果我们有一个型别 `Car`，而且对他们做相等比较是有意义的，那把 `Car` 作成是 `Eq` 的一个 instance 是非常合理的。

这边来看看在 `Prelude` 之中 `Eq` 是怎么被定义的。

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)
```

我们在这边看到了一些奇怪的语法跟关键字。别担心，你一下子就会了解他们的。首先，我们看到 `class Eq a where`，那代表我们定义了一个新的 typeclass 叫做 `Eq`。`a` 是一个型别变量，他代表 `a` 是任何我们在定义 instance 时的型别。他不一定要叫做 `a`。他也不一定非要一个字母不可，只要他是小写就好。然后我们又定义了几个函数。我们并不一定要实作函数的本体，不过必须要写出函数的型别宣告。

如果我们写成 `class Eq equatable where` 还有 `(==) :: equatable -> equatable -> Bool` 这样的形式，对一些人可能比较容易理解。

总之我们实作了 `Eq` 中需要定义的函数本体，只是我们定义他的方式是用交互递归的形式。我们描述两个 `Eq` 的 instance 要相等，那他们就不能不一样，而他们如果不一样，那他们就是不相等。我们其实不必这样写，但很快你会看到这其实是有用的。

如果我们说 `class Eq a where` 然后定义 `(==) :: a -> a -> Bool`，那我们之后检查函数的型别时会发现他的型别是 `(Eq a) => a -> a -> Bool`。

当我们有了 class 以后，可以用来做些什么呢？说实话，不多。不过一旦我们为它写一些 instance，就会有些好功能。来看看下面这个型别：

```haskell
data TrafficLight = Red | Yellow | Green
```

这里定义了红绿灯的状态。请注意这个型别并不是任何 class 的 instance，虽然可以透过 derive 让它成为 `Eq` 或 `Show` 的 instance，但我们打算手工打造。下面展示了如何让一个型别成为 `Eq` 的 instance：

```haskell
instance Eq TrafficLight where
    Red == Red = True
    Green == Green = True
    Yellow == Yellow = True
    _ == _ = False
```

我们使用了 `instance` 这个关键字。class 是用来定义新的 typeclass，而 instance 是用来说明我们要定义某个 typeclass 的 instance。当我们要定义 `Eq`，我们会写 `class Eq a where`，其中 `a` 代表任何型态。我们可以从 instance 的写法：`instance Eq TrafficLight where` 看出来。我们会把 `a` 换成实际的型别。

由于 `==` 是用 `/=` 来定义的，同样的 `/=` 也是用 `==` 来定义。所以我们只需要在 instance 定义中复写其中一个就好了。我们这样叫做定义了一个 minimal complete definition。这是说能让型别符合 class 行为所最小需要实作的函数数量。而 `Eq` 的 minimal complete definition 需要 `==` 或 `/=` 其中一个。而如果 `Eq` 是这样定义的：

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
```

当我们定义 instance 的时候必须要两个函数都实作，因为 Haskell 并不知道这两个函数是怎么关联在一起的。所以 minimal complete definition 在这边是 `==` 跟 `/=`。

你可以看到我们是用模式匹配来实作 `==`。由于不相等的情况比较多，所以我们只写出相等的，最后再用一个 `case` 接住说你不在前面相等的 `case` 的话，那就是不相等。

我们再来写 `Show` 的 instance。要满足 `Show` 的 minimal complete definition，我们必须实作 `show` 函数，他接受一个值并把他转成字串。

```haskell
instance Show TrafficLight where
    show Red = "Red light"
    show Yellow = "Yellow light"
    show Green = "Green light"
```

再一次地，我们用模式匹配来完成我们的任务。我们来看看他是如何运作的。

```haskell
ghci> Red == Red
True
ghci> Red == Yellow
False
ghci> Red `elem` [Red, Yellow, Green]
True
ghci> [Red, Yellow, Green]
[Red light,Yellow light,Green light]
```

如果我们用 `derive` 来自动产生 `Eq` 的话，效果是一样的。不过用 `derive` 来产生 `show` 的话，他会把值构造子转换成字串。但我们这边要的不太一样，我们希望印出像 `"Red light"` 这样的字串，所以我们就必须手动来写出 instance。

你也可以把 typeclass 定义成其他 typeclass 的 subclass。像是 `Num` 的 class 宣告就有点冗长，但我们先看个雏型。

```haskell
class (Eq a) => Num a where
   ...
```

正如我们先前提到的，我们可以在很多地方加上 class constraints。这不过就是在 `class Num a where` 中的 `a` 上，加上他必须要是 `Eq` 的 instance 的限制。这基本上就是在说我们在定义一个型别为 `Num` 之前，必须先为他定义 `Eq` 的 instance。在某个型别可以被视作 `Number` 之前，必须先能被比较相不相等其实是蛮合理的。这就是 subclass 在做的事：帮 class declaration 加上限制。也就是说当我们定义 typeclass 中的函数本体时，我们可以缺省 `a` 是属于 `Eq`，因此能使用 `==`。

但像是 `Maybe` 或是 List 是如何被定义成 typeclass 的 instance 呢？`Maybe` 的特别之处在于他跟 `TrafficLight` 不一样，他不是一个具体的型别。他是一个型别构造子，接受一个型别参数（像是 `Char` 之类的）而构造出一个具体的型别（像是 `Maybe Char` ）。让我们再回顾一下 `Eq` 这个 typeclass：

```haskell
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)
```

从型别宣告来看，可以看到 `a` 必须是一个具体型别，因为所有在函数中的型别都必须是具体型别。(你没办法写一个函数，他的型别是 `a -> Maybe`，但你可以写一个函数，他的型别是 `a -> Maybe a`，或是 `Maybe Int -> Maybe String`) 这就是为什么我们不能写成像这样：

```haskell
instance Eq Maybe where
    ...
```

```haskell
instance Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False
```

这就好像在说我们要把 `Maybe something` 这种东西全部都做成 `Eq` 的 instance。我们的确可以写成 `(Maybe something)`，但我们通常是只用一个字母，这样比较像是 Haskell 的风格。`(Maybe m)` 这边则取代了 `a` 在 `class Eq a where` 的位置。尽管 `Maybe` 不是一个具体的型别。`Maybe m` 却是。指定一个型别参数（在这边是小写的 `m`），我们说我们想要所有像是 `Maybe m` 的都成为 `Eq` 的 instance。

不过这仍然有一个问题。你能看出来吗？ 我们用 `==` 来比较 `Maybe` 包含的东西，但我们并没有任何保证说 `Maybe` 装的东西可以是 `Eq`。这就是为什么我们需要修改我们的 instance 定义：

```haskell
instance (Eq m) => Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False
```

这边我们必须要加上限制。在这个 instance 的宣告中，我们希望所有 `Maybe m` 形式的型别都属于 `Eq`，但只有当 `m` 也属于 `Eq` 的时候。这也是 Haskell 在 derive 的时候做的事。

在大部份情形下，在 typeclass 宣告中的 class constraints 都是要让一个 typeclass 成为另一个 typeclass 的 subclass。而在 instance 宣告中的 class constraint 则是要表达型别的要求限制。举里来说，我们要求 `Maybe` 的内容物也要属于 `Eq`。

当定义 instance 的时候，如果你需要提供具体型别（像是在 `a -> a -> Bool` 中的 `a`），那你必须要加上括号跟型别参数来构造一个具体型别。

要知道你在定义 instance 的时候，型别参数会被取代。`class Eq a where` 中的 `a` 会被取代成真实的型别。所以试着想像把型别放进型别宣告中。`(==) :: Maybe -> Maybe -> Bool` 并非合法。但 `(==) :: (Eq m) => Maybe m -> Maybe m -> Bool` 则是。这是不论我们要定义什么，通用的型别宣告都是 `(==) :: (Eq a) => a -> a -> Bool`

还有一件事要确认。如果你想看看一个 typeclass 有定义哪些 instance。可以在 ghci 中输入 `:info YourTypeClass`。所以输入 `:info Num` 会告诉你这个 typeclass 定义了哪些函数，还有哪些型别属于这个 typeclass。`:info` 也可以查找型别跟型别构造子的信息。如果你输入 `:info Maybe`。他会显示 `Maybe` 所属的所有 typeclass。`:info` 也能告诉你函数的型别宣告。

## yes-no typeclass

在 Javascript 或是其他弱型别的编程语言，你能在 if expression 中摆上任何东西。举例来说，你可以做像下列的事： `if (0) alert("YEAH!") else alert("NO!")`, `if ("") alert ("YEAH!") else alert("NO!")`, `if (false) alert("YEAH") else alert("NO!)` 等等， 而上述所有的片段执行后都会跳出 `NO!`。如果你写 `if ("WHAT") alert ("YEAH") else alert("NO!")`，他会跳出 `YEAH!`，因为 Javascript 认为非空字串会是 true。

尽管使用 `Bool` 来表达布林的语意是比较好的作法。为了有趣起见，我们来试试看模仿 Javascript 的行为。我们先从 typeclass 宣告开始看：

```haskell
class YesNo a where
    yesno :: a -> Bool
```

`YesNo` typeclass 定义了一个函数。这个函数接受一个可以判断为真否的型别的值。而从我们写 `a` 的位置，可以看出来 `a` 必须是一个具体型别。

接下来我们来定义一些 instance。对于数字，我们会假设任何非零的数字都会被当作 `true`，而 0 则当作 `false`。

```haskell
instance YesNo Int where
    yesno 0 = False
    yesno _ = True
```

空的 List (包含字串)代表 `false`，而非空的 List 则代表 `true`。

```haskell
instance YesNo [a] where
    yesno [] = False
    yesno _ = True
```

留意到我们加了一个型别参数 `a` 来让整个 List 是一个具体型别，不过我们并没有对包涵在 List 中的元素的型别做任何额外假设。我们还剩下 `Bool` 可以被作为真假值，要定义他们也很容易：

```haskell
instance YesNo Bool where
    yesno = id
```

你说 `id` 是什么？他不过是标准函式库中的一个函数，他接受一个参数并回传相同的东西。

我们也让 `Maybe a` 成为 `YesNo` 的 instance。

```haskell
instance YesNo (Maybe a) where
    yesno (Just _) = True
    yesno Nothing = False
```

由于我们不必对 `Maybe` 的内容做任何假设，因此并不需要 class constraint。我们只要定义遇到 `Just` 包装过的值就代表 true，而 `Nothing` 则代表 false。这里还是得写出 `(Maybe a)` 而不是只有 `Maybe`，毕竟 `Maybe -> Bool` 的函式并不存在（因为 `Maybe` 并不是具体型别），而 `Maybe a -> Bool` 看起来就合理多了。现在有了这个定义，`Maybe something` 型式的型别都属于 `YesNo` 了，不论 `something` 是什么。

之前我们定义了 `Tree a`，那代表一个二元搜索树。我们可以说一棵空的树是 `false`，而非空的树则是 `true`。

```haskell
instance YesNo (Tree a) where
    yesno EmptyTree = False
    yesno _ = True
```

而一个红绿灯可以代表 yes or no 吗？当然可以。如果他是红灯，那你就会停下来，如果他是绿灯，那你就能走。但如果是黄灯呢？只能说我通常会闯黄灯。

```haskell
instance YesNo TrafficLight where
    yesno Red = False
    yesno _ = True
```

现在我们定义了许多 instance，来试着跑跑看！

```haskell
ghci> yesno $ length []
False
ghci> yesno "haha"
True
ghci> yesno ""
False
ghci> yesno $ Just 0
True
ghci> yesno True
True
ghci> yesno EmptyTree
False
ghci> yesno []
False
ghci> yesno [0,0,0]
True
ghci> :t yesno
yesno :: (YesNo a) => a -> Bool
```

很好，统统是我们预期的结果。我们来写一个函数来模仿 if statement 的行为，但他是运作在 `YesNo` 的型别上。

```haskell
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal yesResult noResult =
    if yesno yesnoVal then yesResult else noResult
```

很直觉吧！他接受一个 yes or no 的值还有两个部份，如果值是代表 "yes"，那第一个部份就会被执行，而如果值是 "no"，那第二个部份就会执行。

```haskell
ghci> yesnoIf [] "YEAH!" "NO!"
"NO!"
ghci> yesnoIf [2,3,4] "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf True "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf (Just 500) "YEAH!" "NO!"
"YEAH!"
ghci> yesnoIf Nothing "YEAH!" "NO!"
"NO!"
```

## Functor typeclass

到目前为止我们看过了许多在标准函式库中的 typeclass。我们操作过 `Ord`，代表可以被排序的东西。我们也操作过 `Eq`，代表可以被比较相等性的事物。也看过 `Show`，代表可以被印成字串来表示的东西。至于 `Read` 则是我们可以把字串转换成型别的动作。不过现在我们要来看一下 `Functor` 这个 typeclass，基本上就代表可以被 map over 的事物。听到这个词你可能会联想到 List，因为 map over list 在 Haskell 中是很常见的操作。你没想错，List 的确是属于 `Functor` 这个 typeclass。

来看看他的实作会是了解 `Functor` 的最佳方式：

```haskell
class Functor f where
    fmap :: (a -> b) -> f a -> f b
```

我们看到他定义了一个函数 `fmap`，而且并没有提供一个缺省的实作。`fmap` 的型别蛮有趣的。到目前为止的我们看过的 typeclass 中的型别变量都是具体型别。就像是 `(==) :: (Eq a) => a -> a -> Bool` 中的 `a` 一样。但现在碰到的 `f` 并不是一个具体型别（一个像是 `Int`, `Bool` 或 `Maybe String`的型别），而是接受一个型别参数的型别构造子。如果要快速回顾的话可以看一下 `Maybe Int` 是一个具体型别，而 `Maybe` 是一个型别构造子，可接受一个型别作为参数。总之，我们知道 `fmap` 接受一个函数，这个函数从一个型别映射到另一个型别，还接受一个 functor 装有原始的型别，然后会回传一个 functor 装有映射后的型别。

如果听不太懂也没关系。当我们看几个范例之后会比较好懂。不过这边 `fmap` 的型别宣告让我们想起类似的东西，就是 `map :: (a -> b) -> [a] -> [b]`。

他接受一个函数，这函数把一个型别的东西映射成另一个。还有一串装有某个型别的 List 变成装有另一个型别的 List。到这边听起来实在太像 functor 了。实际上，`map` 就是针对 List 的 `fmap`。来看看 List 是如何被定义成 `Functor` 的 instance 的。

```haskell
instance Functor [] where
    fmap = map
```

注意到我们不是写成 `instance Functor [a] where`，因为从 `fmap :: (a -> b) -> f a -> f b` 可以知道 `f` 是一个型别构造子，他接受一个型别。而 `[a]` 则已经是一个具体型别（一个拥有某个型别的 List），其中 `[]` 是一个型别构造子，能接受某个型别而构造出像 `[Int]`、`[String]` 甚至是 `[[String]]` 的具体型别。

对于 List，`fmap` 只不过是 `map`，对 List 操作的时候他们都是一样的。

```haskell
map :: (a -> b) -> [a] -> [b]
ghci> fmap (*2) [1..3]
[2,4,6]
ghci> map (*2) [1..3]
[2,4,6]
```

至于当我们对空的 List 操作 `map` 或 `fmap` 呢？我们会得到一个空的 List。他把一个型别为 `[a]` 的空的 List 转成型别为 `[b]` 的空的 List。

可以当作盒子的型别可能就是一个 functor。你可以把 List 想做是一个拥有无限小隔间的盒子。他们可能全部都是空的，已也可能有一部份是满的其他是空的。所以作为一个盒子会具有什么性质呢？例如说 `Maybe a`。他表现得像盒子在于他可能什么东西都没有，就是 `Nothing`，或是可以装有一个东西，像是 `"HAHA"`，在这边就是 `Just "HAHA"`。可以看到 `Maybe` 作为一个 functor 的定义：

```haskell
instance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing = Nothing
```

注意到我们是写 `instance Functor Maybe where` 而不是 `instance Functor (Maybe m) where`，就像我们在写 `YesNo` 时的 `Maybe` 一样。`Functor` 要的是一个接受一个型别参数的型别构造子而不是一个具体型别。如果你把 `f` 代换成 `Maybe`。`fmap` 就会像 `(a -> b) -> Maybe a -> Maybe b`。但如果你把 `f` 代换成 `(Maybe m)`，那他就会像 `(a -> b) -> Maybe m a -> Maybe m b`，这看起来并不合理，因为 `Maybe` 只接受一个型别参数。

总之，`fmap` 的实作是很简单的。如果一个空值是 `Nothing`，那他就会回传 `Nothing`。如果我们 map over 一个空的盒子，我们就会得到一个空的盒子。就像我们 map over 一个空的 List，那我们就会得到一个空的 List。如果他不是一个空值，而是包在 `Just` 中的某个值，那我们就会套用在包在 `Just` 中的值。

```haskell
ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") (Just "Something serious.")
Just "Something serious. HEY GUYS IM INSIDE THE JUST"
ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") Nothing
Nothing
ghci> fmap (*2) (Just 200)
Just 400
ghci> fmap (*2) Nothing
Nothing
```

另外 `Tree a` 的型别也可以被 map over 且被定义成 `Functor` 的一个 instance。他可以被想成是一个盒子，而 `Tree` 的型别构造子也刚好接受单一一个型别参数。如果你把 `fmap` 看作是一个特别为 `Tree` 写的函数，他的型别宣告会长得像这样 `(a -> b) -> Tree a -> Tree b`。不过我们在这边会用到递归。map over 一棵空的树会得到一棵空的树。map over 一棵非空的树会得到一棵被函数映射过的树，他的 root 会先被映射，然后左右子树都分别递归地被函数映射。

```haskell
instance Functor Tree where
    fmap f EmptyTree = EmptyTree
    fmap f (Node x leftsub rightsub) =
        Node (f x) (fmap f leftsub) (fmap f rightsub)
```

```haskell
ghci> fmap (*2) EmptyTree
EmptyTree
ghci> fmap (*4) (foldr treeInsert EmptyTree [5,7,3,2,1,7])
Node 28 (Node 4 EmptyTree (Node 8 EmptyTree (Node 12 EmptyTree (Node 20 EmptyTree EmptyTree)))) EmptyTree
```

那 `Either a b` 又如何？他可以是一个 functor 吗？`Functor` 限制型别构造子只能接受一个型别参数，但 `Either` 却接受两个。聪明的你会想到我可以 partial apply `Either`，先喂给他一个参数，并把另一个参数当作 free parameter。来看看 `Either a` 在标准函式库中是如何被定义的：

```haskell
instance Functor (Either a) where
    fmap f (Right x) = Right (f x)
    fmap f (Left x) = Left x
```

我们在这边做了些什么？你可以看到我们把 `Either a` 定义成一个 instance，而不是 `Either`。那是因为 `Either a` 是一个接受单一型别参数的型别构造子，而 `Either` 则接受两个。如果 `fmap` 是针对 `Either a`，那他的型别宣告就会像是 `(b -> c) -> Either a b -> Either a c`，他又等价于 `(b -> c) -> (Either a) b -> (Either a) c`。在实作中，我们碰到一个 `Right` 的时候会做 `map`，但在碰到 `Left` 的时候却不这样做，为什么呢？如果我们回头看看 `Either a b` 是怎么定义的：

```haskell
data Either a b = Left a | Right b
```

如果我们希望对他们两个都做 `map` 的动作，那 `a` 跟 `b` 必须要是相同的型别。也就是说，如果我们的函数是接受一个字串然后回传另一个字串，而且 `b` 是字串，`a` 是数字，这样的情形是不可行的。而且从观察 `fmap` 的型别也可以知道，当他运作在 `Either` 上的时候，第一个型别参数必须固定，而第二个则可以改变，而其中第一个参数正好就是 `Left` 用的。

我们持续用盒子的比喻也仍然贴切，我们可以把 `Left` 想做是空的盒子在他旁边写上错误消息，说明为什么他是空的。

在 `Data.Map` 中的 Map 也可以被定义成 functor，像是 `Map k v` 的情况下，`fmap` 可以用 `v -> v'` 这样一个函数来 map over `Map k v`，并回传 `Map k v'`。

注意到 `'` 在这边并没有特别的意思，他只是用来表示他跟另一个东西有点像，只有一点点差别而已。

你可以自己试试看把 `Map k` 变成 `Functor` 的一个 instance。

看过了 `Functor` 这个 typeclass，我们知道 typeclass 可以拿来代表高端的概念。我们也练习过不少 partially applying type 跟定义 instance。在下几章中，我们也会看到 functor 必须要遵守的定律。

还有一件事就是 functor 应该要遵守一些定律，这样他们的一些性质才能被保证。如果我们用 `fmap (+1)` 来 map over `[1,2,3,4]`，我们会期望结果会是 `[2,3,4,5]` 而不是反过来变成 `[5,4,3,2]`。如果我们使用 `fmap (\a -> a)`来 map over 一个 list，我们会期待拿回相同的结果。例如说，如果我们给 `Tree` 定义了错误的 functor instance，对 tree 使用 `fmap` 可能会导致二元搜索树的性质丧失，也就是在 root 左边的节点不再比 root 小，在 root 右边的节点不再比 root 大。我们在下面几章会多谈 functor laws。

## Kind

型别构造子接受其他型别作为他的参数，来构造出一个具体型别。这样的行为会让我们想到函数，也是接受一个值当作参数，并回传另一个值。我们也看过型别构造子可以 partially apply （`Either String` 是一个型别构造子，他接受一个型别来构造出一个具体型别，就像 `Either String Int`）。这些都是函数能办到的事。在这个章节中，对于型别如何被套用到型别构造子上，我们会来看一下正式的定义。就像我们之前是用函数的型别来定义出函数是如何套用值的。如果你看不懂的话，你可以跳过这一章，这不会影响你后续的阅读。然而如果你搞懂的话，你会对于型别系统有更进一步的了解。

像是 `3`,`"YEAH"` 或是 `takeWhile` 的值他们都有自己的型别（函数也是值的一种，我们可以把他们传来传去）型别是一个标签，值会把他带着，这样我们就可以推测出他的性质。但型别也有他们自己的标签，叫做 kind。kind 是型别的型别。虽然听起来有点玄妙，不过他的确是个有趣的概念。

那kind可以拿来做什么呢？我们可以在 ghci 中用 `:k` 来得知一个型别的 kind。

```haskell
ghci> :k Int
Int :: *
```

一个星星代表的是什么意思？一个 `*` 代表这个型别是具体型别。一个具体型别是没有任何型别参数，而值只能属于具体型别。而 `*` 的读法叫做 star 或是 type。

我们再看看 `Maybe` 的 kind：

```haskell
ghci> :k Maybe
Maybe :: * -> *
```

`Maybe` 的型别构造子接受一个具体型别（像是 `Int`）然后回传一个具体型别，像是 `Maybe Int`。这就是 kind 告诉我们的信息。就像 `Int -> Int` 代表这个函数接受 `Int` 并回传一个 `Int`。`* -> *` 代表这个型别构造子接受一个具体型别并回传一个具体型别。我们再来对 `Maybe` 套用型别参数后再看看他的 kind 是什么：

```haskell
ghci> :k Maybe Int
Maybe Int :: *
```

正如我们预期的。我们对 `Maybe` 套用了型别参数后会得到一个具体型别（这就是 `* -> *` 的意思）这跟 `:t isUpper` 还有 `:t isUpper 'A'` 的差别有点类似。`isUpper` 的型别是 `Char -> Bool` 而 `isUpper 'A'` 的型别是 `Bool`。而这两种型别，都是 `*` 的 kind。

我们对一个型别使用 `:k` 来得到他的 kind。就像我们对值使用 `:t` 来得到的他的型别一样。就像我们先前说的，型别是值的标签，而 kind 是型别的标签。

我们再来看看其他的 kind

```haskell
ghci> :k Either
Either :: * -> * -> *
```

这告诉我们 `Either` 接受两个具体型别作为参数，并构造出一个具体型别。他看起来也像是一个接受两个参数并回传值的函数型别。型别构造子是可以做 curry 的，所以我们也能 partially apply。

```haskell
ghci> :k Either String
Either String :: * -> *
ghci> :k Either String Int
Either String Int :: *
```

当我们希望定义 `Either` 成为 `Functor` 的 instance 的时候，我们必须先 partial apply，因为 `Functor` 预期有一个型别参数，但 `Either` 却有两个。也就是说，`Functor` 希望型别的 kind 是 `* -> *`，而我们必须先 partial apply `Either` 来得到 kind `* -> *`，而不是最开始的 `* -> * -> *`。我们再来看看 `Functor` 的定义

```haskell
class Functor f where
    fmap :: (a -> b) -> f a -> f b
```

我们看到 `f` 型别变量是接受一个具体型别且构造出一个具体型别的型别。 知道他构造出具体型别是因为是作为函数参数的型别。 从那里我们可以推测出一个型别要是属于 `Functor` 必须是 `* -> *` kind。

现在我们来练习一下。来看看下面这个新定义的 typeclass。

```haskell
class Tofu t where
    tofu :: j a -> t a j
```

这看起来很怪。我们干嘛要为这个奇怪的 typeclass 定义 instance？我们可以来看看他的 kind 是什么？由于 `j a` 被当作 `tofu` 这个函数的参数的型别，所以 `j a` 一定是 `*` kind。我们假设 `a` 是 `*` kind，那 `j` 就会是 `* -> *` 的 kind。我们看到 `t` 由于是函数的回传值，一定是接受两个型别参数的型别。而知道 `a` 是 `*`，`j` 是 `* -> *`，我们可以推测出 `t`是`* -> (* -> *) -> *`。也就是说他接受一个具体型别 `a`，一个接受单一参数的型别构造子 `j`，然后产生出一个具体型别。

我们再来定义出一个型别具有 `* -> (* -> *) -> *` 的 kind，下面是一种定义的方法：

```haskell
data Frank a b  = Frank {frankField :: b a} deriving (Show)
```

我们怎么知道这个型别具有 `* -> (* -> *) -> *` 的 kind 呢？ADT 中的字段是要来塞值的，所以他们必须是 `*` kind。我们假设 `a` 是 `*`，那 `b` 就是接受一个型别参数的 kind `* -> *`。现在我们知道 `a` 跟 `b` 的 kind 了，而他们又是 `Frank` 的型别参数，所以我们知道 `Frank` 会有 `* -> (* -> *) -> *` 的 kind。第一个 `*` 代表 `a`，而 `(* -> *)` 代表 `b`。我们构造些 `Frank` 的值并检查他们的型别吧：

```haskell
ghci> :t Frank {frankField = Just "HAHA"}
Frank {frankField = Just "HAHA"} :: Frank [Char] Maybe
ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}
Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree
ghci> :t Frank {frankField = "YES"}
Frank {frankField = "YES"} :: Frank Char []
```

由于 `frankField` 具有 `a b` 的型别。他的值必定有一个类似的型别。他们可能是 `Just "HAHA"`，也就有 `Maybe [Char]` 的型别，或是他们可能是 `['Y','E','S']`，他的型别是 `[Char]`。（如果我们是用自己定义的 List 型别的话，那就会是 `List Char`）。我们看到 `Frank` 值的型别对应到 `Frank` 的 kind。`[Char]` 具有 `*` 的 kind，而 `Maybe` 则是 `* -> *`。由于结果必须是个值，也就是他必须要是具体型别，因使他必须 fully applied，因此每个 `Frank blah blaah` 的值都会是 `*` 的 kind。

要把 `Frank` 定义成 `Tofu` 的 instance 也是很简单。我们看到 `tofu` 接受 `j a`（例如 `Maybe Int`）并回传 `t a j`。所以我们将 `Frank` 代入 `t`，就得到 `Frank Int Maybe`。

```haskell
instance Tofu Frank where
    tofu x = Frank x
```

```haskell
ghci> tofu (Just 'a') :: Frank Char Maybe
Frank {frankField = Just 'a'}
ghci> tofu ["HELLO"] :: Frank [Char] []
Frank {frankField = ["HELLO"]}
```

这并不是很有用，但让我们做了不少型别的练习。再来看看下面的型别：

```haskell
data Barry t k p = Barry { yabba :: p, dabba :: t k }
```

我们想要把他定义成 `Functor` 的 instance。`Functor` 希望是 `* -> *` 的型别，但 `Barry` 并不是那种 kind。那 `Barry` 的 kind 是什么呢？我们可以看到他接受三个型别参数，所以会是 `something -> something -> something -> *`。`p` 是一个具体型别因此是 `*`。至于 `k`，我们假设他是 `*`，所以 `t` 会是 `* -> *`。现在我们把这些代入 `something`，所以 kind 就变成 `(* -> *) -> * -> * -> *`。我们用 ghci 来检查一下。

```haskell
ghci> :k Barry
Barry :: (* -> *) -> * -> * -> *
```

我们猜对了！现在要把这个型别定义成 `Functor`，我们必须先 partially apply 头两个型别参数，这样我们就会是 `* -> *` 的 kind。这代表 instance 定义会是 `instance Functor (Barry a b) where`。如果我们看 `fmap` 针对 `Barry` 的型别，也就是把 `f` 代换成 `Barry c d`，那就会是 `fmap :: (a -> b) -> Barry c d a -> Barry c d b`。第三个 `Barry` 的型别参数是对于任何型别，所以我们并不牵扯进他。

```haskell
instance Functor (Barry a b) where
    fmap f (Barry {yabba = x, dabba = y}) = Barry {yabba = f x, dabba = y}
```

我们把 `f` map 到第一个字段。

在这一个章节中，我们看到型别参数是怎么运作的，以及正如我们用型别来定义出函数的参数，我们也用 kind 是来定义他。我们看到函数跟型别构造子有许多彼此相像的地方。然而他们是两个完全不同的东西。当我们在写一般实用的 Haskell 程序时，你几乎不会碰到需要动到 kind 的东西，也不需要动脑去推敲 kind。通常你只需要在定义 instance 时 partially apply 你自己的 `* -> *` 或是 `*` 型别，但知道背后运作的原理也是很好的。知道型别本身也有自己的型别也是很有趣的。如果你实在不懂这边讲的东西，也可以继续阅读下去。但如果你能理解，那你就会理解 Haskell 型别系统的一大部份。


# ch09


# 输入与输出

![](/files/-MlVFmxRM3CtXSv8xOJN)

我们已经说明了 Haskell 是一个纯粹函数式语言。虽说在命令式语言中我们习惯给电脑执行一连串指令，在函数式语言中我们是用定义东西的方式进行。在 Haskell 中，一个函数不能改变状态，像是改变一个变量的内容。（当一个函数会改变状态，我们说这函数是有副作用的。）在 Haskell 中函数唯一可以做的事是根据我们给定的参数来算出结果。如果我们用同样的参数调用两次同一个函数，它会回传相同的结果。尽管这从命令式语言的角度来看是蛮大的限制，我们已经看过它可以达成多么酷的效果。在一个命令式语言中，编程语言没办法给你任何保证在一个简单如打印出几个数字的函数不会同时烧掉你的房子，绑架你的狗并刮伤你车子的烤漆。例如，当我们要建立一棵二元树的时候，我们并不插入一个节点来改变原有的树。由于我们无法改变状态，我们的函数实际上回传了一棵新的二元树。

函数无法改变状态的好处是它让我们促进了我们理解程序的容易度，但同时也造成了一个问题。假如说一个函数无法改变现实世界的状态，那它要如何打印出它所计算的结果？毕竟要告诉我们结果的话，它必须要改变输出设备的状态（譬如说屏幕），然后从屏幕传达到我们的脑，并改变我们心智的状态。

不要太早下结论，Haskell 实际上设计了一个非常聪明的系统来处理有副作用的函数，它漂亮地将我们的程序区分成纯粹跟非纯粹两部分。非纯粹的部分负责跟键盘还有屏幕沟通。有了这区分的机制，在跟外界沟通的同时，我们还是能够有效运用纯粹所带来的好处，像是惰性求值、容错性跟模块性。

## Hello, world!

![](/files/-LjUmt6tk6b7iTq7FCjo)

到目前为止我们都是将函数加载 GHCi 中来测试，像是标准函式库中的一些函式。但现在我们要做些不一样的，写一个真实跟世界交互的 Haskell 程序。当然不例外，我们会来写个 "hello world"。

现在，我们把下一行打到你熟悉的编辑器中

```haskell
main = putStrLn "hello, world"
```

我们定义了一个 `main`，并在里面以 `"hello, world"` 为参数调用了 `putStrLn`。看起来没什么大不了，但不久你就会发现它的奥妙。把这程序存成 `helloworld.hs`。

现在我们将做一件之前没做过的事：编译你的程序。打开你的终端并切换到包含 `helloworld.hs` 的目录，并输入下列指令。

```haskell
$ ghc --make helloworld
[1 of 1] Compiling Main                 ( helloworld.hs, hellowowlrd.o )
Linking helloworld ...
```

顺利的话你就会得到如上的消息，接着你便可以执行你的程序 `./helloworld`

```haskell
$ ./helloworld
hello, world
```

这就是我们第一个编译成功并打印出字串到屏幕的程序。很简单吧。

让我们来看一下我们究竟做了些什么，首先来看一下 `putStrLn` 函数的型态：

```haskell
ghci> :t putStrLn
putStrLn :: String -> IO ()
ghci> :t putStrLn "hello, world"
putStrLn "hello, world" :: IO ()
```

我们可以这么解读 `putStrLn` 的型态：`putStrLn` 接受一个字串并回传一个 I/O action，这 I/O action 包含了 `()` 的型态。（即空的 tuple，或者是 unit 型态）。一个 I/O action 是一个会造成副作用的动作，常是指读取输入或输出到屏幕，同时也代表会回传某些值。在屏幕打印出几个字串并没有什么有意义的回传值可言，所以这边用一个 `()` 来代表。

那究竟 I/O action 会在什么时候被触发呢？这就是 `main` 的功用所在。一个 I/O action 会在我们把它绑定到 `main` 这个名字并且执行程序的时候触发。

把整个程序限制在只能有一个 I/O action 看似是个极大的限制。这就是为什么我们需要 do 表示法来将所有 I/O action 绑成一个。来看看下面这个例子。

```haskell
main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")
```

新的语法，有趣吧！它看起来就像一个命令式的程序。如果你编译并执行它，它便会照你预期的方式执行。我们写了一个 do 并且接着一连串指令，就像写个命令式程序一般，每一步都是一个 I/O action。将所有 I/O action 用 do 绑在一起变成了一个大的 I/O action。这个大的 I/O action 的型态是 `IO ()`，这完全是由最后一个 I/O action 所决定的。

这就是为什么 `main` 的型态永远都是 `main :: IO something`，其中 `something` 是某个具体的型态。按照惯例，我们通常不会把 `main` 的型态在程序中写出来。

另一个有趣的事情是第三行 `name <- getLine`。它看起来像是从输入读取一行并存到一个变量 `name` 之中。真的是这样吗？我们来看看 `getLine` 的型态吧

```haskell
ghci> :t getLine
getLine :: IO String
```

![](/files/-LjUmt6wFoUi8l7q7xwD)

我们可以看到 `getLine` 是一个回传 `String` 的 I/O action。因为它会等用户输入某些字串，这很合理。那 `name <- getLine` 又是如何？你能这样解读它：执行一个 I/O action `getLine` 并将它的结果绑定到 `name` 这个名字。`getLine` 的型态是 `IO String`，所以 `name` 的型态会是 `String`。你能把 I/O action 想成是一个长了脚的盒子，它会跑到真实世界中替你做某些事，像是在墙壁上涂鸦，然后带回来某些数据。一旦它带了某些数据给你，打开盒子的唯一办法就是用 `<-`。而且如果我们要从 I/O action 拿出某些数据，就一定同时要在另一个 I/O action 中。这就是 Haskell 如何漂亮地分开纯粹跟不纯粹的程序的方法。`getLine` 在这样的意义下是不纯粹的，因为执行两次的时候它没办法保证会回传一样的值。这也是为什么它需要在一个 `IO` 的型态建构子中，那样我们才能在 I/O action 中取出数据。而且任何一段程序一旦依赖着 I/O 数据的话，那段程序也会被视为 I/O code。

但这不表示我们不能在纯粹的代码中使用 I/O action 回传的数据。只要我们绑定它到一个名字，我们便可以暂时地使用它。像在 `name <- getLine` 中 `name` 不过是一个普通字串，代表在盒子中的内容。我们能将这个普通的字串传给一个极度复杂的函数，并回传你一生会有多少财富。像是这样：

```haskell
main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn $ "Read this carefully, because this is your future: " ++ tellFortune name
```

`tellFortune` 并不知道任何 I/O 有关的事，它的型态只不过是 `String -> String`。

再来看看这段代码吧，他是合法的吗?

```haskell
nameTag = "Hello, my name is " ++ getLine
```

如果你回答不是，恭喜你。如果你说是，你答错了。这么做不对的理由是 `++` 要求两个参数都必须是串列。他左边的参数是 `String`，也就是 `[Char]`。然而 `getLine` 的型态是 `IO String`。你不能串接一个字串跟 I/O action。我们必须先把 `String` 的值从 I/O action 中取出，而唯一可行的方法就是在 I/O action 中使用 `name <- getLine`。如果我们需要处理一些非纯粹的数据，那我们就要在非纯粹的环境中做。所以我们最好把 I/O 的部分缩减到最小的比例。

每个 I/O action 都有一个值封装在里面。这也是为什么我们之前的程序可以这么写：

```haskell
main = do
    foo <- putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn ("Hey " ++ name ++ ", you rock!")
```

然而，`foo` 只会有一个 `()` 的值，所以绑定到 `foo` 这个名字似乎是多余的。另外注意到我们并没有绑定最后一行的 `putStrLn` 给任何名字。那是因为在一个 do block 中，最后一个 action 不能绑定任何名字。我们在之后讲解 Monad 的时候会说明为什么。现在你可以先想成 do block 会自动从最后一个 action 取出值并绑定给他的结果。

除了最后一行之外，其他在 do 中没有绑定名字的其实也可以写成绑定的形式。所以 `putStrLn "BLAH"` 可以写成 `_ <- putStrLn "BLAH"`。但这没什么实际的意义，所以我们宁愿写成 `putStrLn something`。

初学者有时候会想错

```haskell
    name = getLine
```

以为这行会读取输入并给他绑定一个名字叫 `name` 但其实只是把 `getLine` 这个 I/O action 指定一个名字叫 `name` 罢了。记住，要从一个 I/O action 中取出值，你必须要在另一个 I/O action 中将他用 `<-` 绑定给一个名字。

I/O actions 只会在绑定给 `main` 的时候或是在另一个用 do 串起来的 I/O action 才会执行。你可以用 do 来串接 I/O actions，再用 do 来串接这些串接起来的 I/O actions。不过只有最外面的 I/O action 被指定给 main 才会触发执行。

喔对，其实还有另外一个情况。就是在 GHCi 中输入一个 I/O action 并按下 Enter 键，那也会被执行

```haskell
ghci> putStrLn "HEEY"
HEEY
```

就算我们只是在 GHCi 中打几个数字或是调用一个函数，按下 Enter 就会计算它并调用 `show`，再用 `putStrLn` 将字串打印出在终端上。

还记得 let binding 吗？如果不记得，回去温习一下这个章节。它们的形式是 `let bindings in expression`，其中 `bindings` 是 expression 中的名字、`expression` 则是被运用到这些名字的算式。我们也提到了 list comprehensions 中，`in` 的部份不是必需的。你能够在 do blocks 中使用 let bindings 如同在 list comprehensions 中使用它们一样，像这样：

```haskell
import Data.Char

main = do
    putStrLn "What's your first name?"
    firstName <- getLine
    putStrLn "What's your last name?"
    lastName <- getLine
    let bigFirstName = map toUpper firstName
        bigLastName = map toUpper lastName
    putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?"
```

注意我们是怎么编排在 do block 中的 I/O actions，也注意到我们是怎么编排 let 跟其中的名字的，由于对齐在 Haskell 中并不会被无视，这么编排才是好的习惯。我们的程序用 `map toUpper firstName` 将 `"John"` 转成大写的 `"JOHN"`，并将大写的结果绑定到一个名字上，之后在输出的时候参考到了这个名字。

你也许会问究竟什么时候要用 `<-`，什么时候用 let bindings？记住，`<-` 是用来运算 I/O actions 并将他的结果绑定到名称。而 `map toUpper firstName` 并不是一个 I/O action。他只是一个纯粹的 expression。所以总结来说，当你要绑定 I/O actions 的结果时用 `<-`，而对于纯粹的 expression 使用 let bindings。对于错误的 `let firstName = getLine`，我们只不过是把 `getLine` 这个 I/O actions 给了一个不同的名字罢了。最后还是要用 `<-` 将结果取出。

现在我们来写一个会一行一行不断地读取输入，并将读进来的字反过来输出到屏幕上的程序。程序会在输入空白行的时候停止。

```haskell
main = do
    line <- getLine
    if null line
        then return ()
        else do
            putStrLn $ reverseWords line
            main

reverseWords :: String -> String
reverseWords = unwords . map reverse . words
```

在分析这段程序前，你可以执行看看来感受一下程序的运行。

首先，我们来看一下 `reverseWords`。他不过是一个普通的函数，假如接受了个字串 `"hey there man"`，他会先调用 `words` 来产生一个字的串列 `["hey", "there", "man"]`。然后用 `reverse` 来 map 整个串列，得到 `["yeh", "ereht", "nam"]`，接着用 `unwords` 来得到最终的结果 `"yeh ereht nam"`。这些用函数合成来简洁的表达。如果没有用函数合成，那就会写成丑丑的样子 `reverseWords st = unwords (map reverse (words st))`

那 `main` 又是怎么一回事呢？首先，我们用 `getLine` 从终端读取了一行，并把这行输入取名叫 `line`。然后接着一个条件式 expression。记住，在 Haskell 中 if 永远要伴随一个 else，这样每个 expression 才会有值。当 if 的条件是 true （也就是输入了一个空白行），我们便执行一个 I/O action，如果 if 的条件是 false，那 else 底下的 I/O action 被执行。这也就是说当 if 在一个 I/O do block 中的时候，长的样子是 `if condition then I/O action else I/O action`。

我们首先来看一下在 else 中发生了什么事。由于我们在 else 中只能有一个 I/O action，所以我们用 do 来将两个 I/O actions 绑成一个，你可以写成这样：

```haskell
else (do
    putStrLn $ reverseWords line
    main)
```

这样可以明显看到整个 do block 可以看作一个 I/O action，只是比较丑。但总之，在 do block 里面，我们依序调用了 `getLine` 以及 `reverseWords`，在那之后，我们递归调用了 `main`。由于 main 也是一个 I/O action，所以这不会造成任何问题。调用 `main` 也就代表我们回到程序的起点。

那假如 `null line` 的结果是 true 呢？也就是说 then 的区块被执行。我们看一下区块里面有 `then return ()`。如果你是从 C、Java 或 Python 过来的，你可能会认为 `return` 不过是作一样的事情便跳过这一段。但很重要的： `return` 在 Hakell 里面的意义跟其他语言的 `return` 完全不同！他们有相同的样貌，造成了许多人搞错，但确实他们是不一样的。在命令式语言中，`return` 通常结束 method 或 subroutine 的执行，并且回传某个值给调用者。在 Haskell 中，他的意义则是利用某个 pure value 造出 I/O action。用之前盒子的比喻来说，就是将一个 value 装进箱子里面。产生出的 I/O action 并没有作任何事，只不过将 value 包起来而已。所以在 I/O 的情况下来说，`return "haha"` 的型态是 `IO String`。将 pure value 包成 I/O action 有什么实质意义呢？为什么要弄成 `IO` 包起来的值？这是因为我们一定要在 else 中摆上某些 I/O action，所以我们才用 `return ()` 做了一个没作什么事情的 I/O action。

在 I/O do block 中放一个 `return` 并不会结束执行。像下面这个程序会执行到底。

```haskell
main = do
    return ()
    return "HAHAHA"
    line <- getLine
    return "BLAH BLAH BLAH"
    return 4
    putStrLn line
```

所有在程序中的 `return` 都是将 value 包成 I/O actions，而且由于我们没有将他们绑定名称，所以这些结果都被忽略。我们能用 `<-` 与 `return` 来达到绑定名称的目的。

```haskell
main = do
    a <- return "hell"
    b <- return "yeah!"
    putStrLn $ a ++ " " ++ b
```

可以看到 `return` 与 `<-` 作用相反。`return` 把 value 装进盒子中，而 `<-` 将 value 从盒子拿出来，并绑定一个名称。不过这么做是有些多余，因为你可以用 let bindings 来绑定

```haskell
main = do
    let a = "hell"
        b = "yeah"
    putStrLn $ a ++ " " ++ b
```

在 I/O do block 中需要 `return` 的原因大致上有两个：一个是我们需要一个什么事都不做的 I/O action，或是我们不希望这个 do block 形成的 I/O action 的结果值是这个 block 中的最后一个 I/O action，我们希望有一个不同的结果值，所以我们用 `return` 来作一个 I/O action 包了我们想要的结果放在 do block 的最后。

在我们接下去讲文件之前，让我们来看看有哪些实用的函数可以处理 I/O。

`putStr` 跟 `putStrLn` 几乎一模一样，都是接受一个字串当作参数，并回传一个 I/O action 打印出字串到终端上，只差在 `putStrLn` 会换行而 `putStr` 不会罢了。

```haskell
main = do putStr "Hey, "
          putStr "I'm "
          putStrLn "Andy!"
```

```haskell
$ runhaskell putstr_test.hs
Hey, I'm Andy!
```

他的 type signature 是 `putStr :: String -> IO ()`，所以是一个包在 I/O action 中的 unit。也就是空值，没有办法绑定他。

`putChar` 接受一个字符，并回传一个 I/O action 将他打印到终端上。

```haskell
main = do putChar 't'
          putChar 'e'
          putChar 'h'
```

```haskell
$ runhaskell putchar_test.hs
teh
```

`putStr` 实际上就是 `putChar` 递归定义出来的。`putStr` 的边界条件是空字串，所以假设我们打印一个空字串，那他只是回传一个什么都不做的 I/O action，像 `return ()`。如果打印的不是空字串，那就先用 `putChar` 打印出字串的第一个字符，然后再用 `putStr` 打印出字串剩下部份。

```haskell
putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do
    putChar x
    putStr xs
```

看看我们如何在 I/O 中使用递归，就像我们在 pure code 中所做的一样。先定义一个边界条件，然后再思考剩下如何作。

`print` 接受任何是 `Show` typeclass 的 instance 的型态的值，这代表我们知道如何用字串表示他，调用 `show` 来将值变成字串然后将其输出到终端上。基本上，他就是 `putStrLn . show`。首先调用 `show` 然后把结果喂给 `putStrLn`，回传一个 I/O action 打印出我们的值。

```haskell
main = do print True
          print 2
          print "haha"
          print 3.2
          print [3,4,3]
```

```haskell
$ runhaskell print_test.hs
True
2
"haha"
3.2
[3,4,3]
```

就像你看到的，这是个很方便的函数。还记得我们提到 I/O actions 只有在 `main` 中才会被执行以及在 GHCI 中运算的事情吗？当我们用键盘打了些值，像 `3` 或 `[1,2,3]` 并按下 Enter，GHCI 实际上就是用了 `print` 来将这些值输出到终端。

```haskell
ghci> 3
3
ghci> print 3
3
ghci> map (++"!") ["hey","ho","woo"]
["hey!","ho!","woo!"]
ghci> print (map (++"!") ["hey", "ho", "woo"])
["hey!","ho!","woo!"]
```

当我们需要打印出字串，我们会用 `putStrLn`，因为我们不想要周围有引号，但对于输出值来说，`print` 才是最常用的。

`getChar` 是一个从输入读进一个字符的 I/O action，因此他的 type signature 是 `getChar :: IO Char`，代表一个 I/O action 的结果是 `Char`。注意由于缓冲区的关系，只有当 Enter 被按下的时候才会触发读取字符的行为。

```haskell
main = do
    c <- getChar
    if c /= ' '
        then do
            putChar c
            main
        else return ()
```

这程序看起来像是读取一个字符并检查他是否为一个空白。如果是的话便停止，如果不是的话便打印到终端上并重复之前的行为。在某种程度上来说也不能说错，只是结果不如你预期而已。来看看结果吧。

```haskell
$ runhaskell getchar_test.hs
hello sir
hello
```

上面的第二行是输入。我们输入了 `hello sir` 并按下了 Enter。由于缓冲区的关系，程序是在我们按了 Enter 后才执行而不是在某个输入字符的时候。一旦我们按下了 Enter，那他就把我们直到目前输入的一次做完。

`when` 这函数可以在 `Control.Monad` 中找到他 (你必须 `import Contorl.Monad` 才能使用他)。他在一个 do block 中看起来就像一个控制流程的 statement，但实际上他的确是一个普通的函数。他接受一个 boolean 值跟一个 I/O action。如果 boolean 值是 `True`，便回传我们传给他的 I/O action。如果 boolean 值是 `False`，便回传 `return ()`，即什么都不做的 I/O action。我们接下来用 `when` 来改写我们之前的程序。

```haskell
import Control.Monad

main = do
    c <- getChar
    when (c /= ' ') $ do
        putChar c
        main
```

就像你看到的，他可以将 `if something then do some I/O action else return ()` 这样的模式封装起来。

`sequence` 接受一串 I/O action，并回传一个会依序执行他们的 I/O action。运算的结果是包在一个 I/O action 的一连串 I/O action 的运算结果。他的 type signature 是 `sequence :: [IO a] -> IO [a]`

```haskell
main = do
    a <- getLine
    b <- getLine
    c <- getLine
    print [a,b,c]
```

其实可以写成

```haskell
main = do
    rs <- sequence [getLine, getLine, getLine]
    print rs
```

所以 `sequence [getLine, getLine, getLine]` 作成了一个执行 `getLine` 三次的 I/O action。如果我们对他绑定一个名字，结果便是这串结果的串列。也就是说，三个用户输入的东西组成的串列。

一个常见的使用方式是我们将 `print` 或 `putStrLn` 之类的函数 map 到串列上。`map print [1,2,3,4]` 这个动作并不会产生一个 I/O action，而是一串 I/O action，就像是 `[print 1, print 2, print 3, print 4]`。如果我们将一串 I/O action 变成一个 I/O action，我们必须用 `sequence`

```haskell
ghci> sequence (map print [1,2,3,4,5])
1
2
3
4
5
[(),(),(),(),()]
```

那 `[(),(),(),(),()]` 是怎么回事？当我们在 GHCI 中运算 I/O action，他会被执行并把结果打印出来，唯一例外是结果是 `()` 的时候不会被打印出。这也是为什么 `putStrLn "hehe"` 在 GHCI 中只会打印出 `hehe`（因为 `putStrLn "hehe"` 的结果是 `()`）。但当我们使用 `getLine` 时，由于 `getLine` 的型态是 `IO String`，所以结果会被打印出来。

由于对一个串列 map 一个回传 I/O action 的函数，然后再 sequence 他这个动作太常用了。所以有一些函数在函式库中 `mapM` 跟 `mapM_`。`mapM` 接受一个函数跟一个串列，将对串列用函数 map 然后 sequence 结果。`mapM_` 也作同样的事，只是他把运算的结果丢掉而已。在我们不关心 I/O action 结果的情况下，`mapM_` 是最常被使用的。

```haskell
ghci> mapM print [1,2,3]
1
2
3
[(),(),()]
ghci> mapM_ print [1,2,3]
1
2
3
```

`forever` 接受一个 I/O action 并回传一个永远作同一件事的 I/O action。你可以在 `Control.Monad` 中找到他。下面的程序会不断地要用户输入些东西，并把输入的东西转成大写输出到屏幕上。

```haskell
import Control.Monad
import Data.Char

main = forever $ do
    putStr "Give me some input: "
    l <- getLine
    putStrLn $ map toUpper l
```

在 `Control.Monad` 中的 `forM` 跟 `mapM` 的作用一样，只是参数的顺序相反而已。第一个参数是串列，而第二个则是函数。这有什么用？在一些有趣的情况下还是有用的：

```haskell
import Control.Monad

main = do
    colors <- forM [1,2,3,4] (\a -> do
        putStrLn $ "Which color do you associate with the number " ++ show a ++ "?"
        color <- getLine
        return color)
    putStrLn "The colors that you associate with 1, 2, 3 and 4 are: "
    mapM putStrLn colors
```

`(\a -> do ...)` 是接受一个数字并回传一个 I/O action 的函数。我们必须用括号括住他，不然 lambda 会贪心 match 的策略会把最后两个 I/O action 也算进去。注意我们在 do block 里面 `return color`。我们那么作是让 do block 的结果是我们选的颜色。实际上我们并不需那么作，因为 `getLine` 已经达到我们的目的。先 `color <- getLine` 再 `return color` 只不过是把值取出再包起来，其实是跟 `getLine` 效果相当。`forM` 产生一个 I/O action，我们把结果绑定到 `colors` 这名称。`colors` 是一个普通包含字串的串列。最后，我们用 `mapM putStrLn colors` 打印出所有颜色。

你可以把 `forM` 的意思想成将串列中的每个元素作成一个 I/O action。至于每个 I/O action 实际作什么就要看原本的元素是什么。然后，执行这些 I/O action 并将结果绑定到某个名称上。或是直接将结果忽略掉。

```haskell
$ runhaskell from_test.hs
Which color do you associate with the number 1?
white
Which color do you associate with the number 2?
blue
Which color do you associate with the number 3?
red
Which color do you associate with the number 4?
orange
The colors that you associate with 1, 2, 3 and 4 are:
white
blue
red
orange
```

其实我们也不是一定要用到 `forM`，只是用了 `forM` 程序会比较容易理解。正常来讲是我们需要在 map 跟 sequence 的时候定义 I/O action 的时候使用 `forM`，同样地，我们也可以将最后一行写成 `forM colors putStrLn`。

在这一节，我们学会了输入与输出的基础。我们也了解了什么是 I/O action，他们是如何帮助我们达成输入与输出的目的。这边重复一遍，I/O action 跟其他 Haskell 中的 value 没有两样。我们能够把他当参数传给函式，或是函式回传 I/O action。他们特别之处在于当他们是写在 `main` 里面或 GHCI 里面的时候，他们会被执行，也就是实际输出到你屏幕或输出音效的时候。每个 I/O action 也能包着一个从真实世界拿回来的值。

不要把像是 `putStrLn` 的函式想成接受字串并输出到屏幕。要想成一个函式接受字串并回传一个 I/O action。当 I/O action 被执行的时候，会漂亮地打印出你想要的东西。

## 文件与字符流

![](/files/-MlVFmxhuPYRoS5VIpft)

`getChar` 是一个读取单一字符的 I/O action。`getLine` 是一个读取一行的 I/O action。这是两个非常直觉的函式，多数编程语言也有类似这两个函式的 statement 或 function。但现在我们来看看 *getContents*。`getContents` 是一个从标准输入读取直到 end-of-file 字符的 I/O action。他的型态是 `getContents :: IO String`。最酷的是 `getContents` 是惰性 I/O (Lazy I/O)。当我们写了 `foo <- getContents`，他并不会马上读取所有输入，将他们存在 memory 里面。他只有当你真的需要输入数据的时候才会读取。

当我们需要重导一个程序的输出到另一个程序的输入时，`getContents` 非常有用。假设我们有下面一个文本档：

```haskell
I'm a lil' teapot
What's with that airplane food, huh?
It's so small, tasteless
```

还记得我们介绍 `forever` 时写的小程序吗？会把所有输入的东西转成大写的那一个。为了防止你忘记了，这边再重复一遍。

```haskell
import Control.Monad
import Data.Char

main = forever $ do
    putStr "Give me some input: "
    l <- getLine
    putStrLn $ map toUpper l
```

将我们的程序存成 `capslocker.hs` 然后编译他。然后用 Unix 的 Pipe 将文本档喂给我们的程序。我们使用的是 GNU 的 cat，会将指定的文件输出到屏幕。

```haskell
$ ghc --make capslocker
[1 of 1] Compiling Main             ( capslocker.hs, capslocker.o )
Linking capslocker ...
$ cat haiku.txt
I'm a lil' teapot
What's with that airplane food, huh?
It's so small, tasteless
$ cat haiku.txt | ./capslocker
I'M A LIL' TEAPOT
WHAT'S WITH THAT AIRPLANE FOOD, HUH?
IT'S SO SMALL, TASTELESS
capslocker <stdin>: hGetLine: end of file
```

就如你看到的，我们是用 `|` 这符号来将某个程序的输出 piping 到另一个程序的输入。我们做的事相当于 run 我们的 capslocker，然后将 haiku 的内容用键盘打到终端上，最后再按 Ctrl-D 来代表 end-of-file。这就像执行 cat haiku.txt 后大喊，嘿，不要把内容打印到终端上，把内容塞到 capslocker！

我们用 `forever` 在做的事基本上就是将输入经过转换后变成输出。用 `getContents` 的话可以让我们的程序更加精炼。

```haskell
import Data.Char

main = do
    contents <- getContents
    putStr (map toUpper contents)
```

我们将 `getContents` 取回的字串绑定到 `contents`。然后用 `toUpper` map 到整个字串后打印到终端上。记住字串基本上就是一串惰性的串列 (list)，同时 `getContents` 也是惰性 I/O，他不会一口气读入内容然后将内容存在内存中。实际上，他会一行一行读入并输出大写的版本，这是因为输出才是真的需要输入的数据的时候。

```haskell
$ cat haiku.txt | ./capslocker
I'M A LIL' TEAPOT
WHAT'S WITH THAT AIRPLAN FOOD, HUH?
IT'S SO SMALL, TASTELESS
```

很好，程序运作正常。假如我们执行 capslocker 然后自己打几行字呢？

```haskell
$ ./capslocker
hey ho
HEY HO
lets go
LETS GO
```

按下 Ctrl-D 来离开环境。就像你看到的，程序是一行一行将我们的输入打印出来。当 `getContent` 的结果被绑定到 `contents` 的时候，他不是被表示成在内存中的一个字串，反而比较像是他有一天会是字串的一个承诺。当我们将 `toUpper` map 到 `contents` 的时候，便也是一个函数被承诺将会被 map 到内容上。最后 `putStr` 则要求先前的承诺说，给我一行大写的字串吧。实际上还没有任何一行被取出，所以便跟 `contents` 说，不如从终端那边取出些字串吧。这才是 `getContents` 真正从终端读入一行并把这一行交给程序的时候。程序便将这一行用 `toUpper` 处理并交给 `putStr`，`putStr` 则打印出他。之后 `putStr` 再说：我需要下一行。整个步骤便再重复一次，直到读到 end-of-file 为止。

接着我们来写个程序，读取输入，并只打印出少于十个字符的行。

```haskell
main = do
    contents <- getContents
    putStr (shortLinesOnly contents)

shortLinesOnly :: String -> String
shortLinesOnly input =
    let allLines = lines input
        shortLines = filter (\line -> length line < 10) allLines
        result = unlines shortLines
    in result
```

我们把 I/O 部份的代码弄得很短。由于程序的行为是接某些输入，作些处理然后输出。我们可以把他想成读取输入，调用一个函数，然后把函数的结果输出。

`shortLinesOnly` 的行为是这样：拿到一个字串，像是 `"short\nlooooooooooooooong\nshort again"`。这字串有三行，前后两行比较短，中间一行很常。他用 `lines` 把字串分成 `["short", "looooooooooooooong", "short again"]`，并把结果绑定成 `allLines`。然后过滤这些字串，只有少于十个字符的留下，`["short", "short again"]`，最后用 `unlines` 把这些字串用换行接起来，形成 `"short\nshort again"`

```haskell
i'm short
so am i
i am a loooooooooong line!!!
yeah i'm long so what hahahaha!!!!!!
short line
loooooooooooooooooooooooooooong
short
```

```haskell
$ ghc --make shortlinesonly
[1 of 1] Compiling Main             ( shortlinesonly.hs, shortlinesonly.o )
Linking shortlinesonly ...
$ cat shortlines.txt | ./shortlinesonly
i'm short
so am i
short
```

我们把 shortlines.txt 的内容经由 pipe 送给 shortlinesonly，结果就如你看到，我们只有得到比较短的行。

从输入那一些字串，经由一些转换然后输出这样的模式实在太常用了。常用到甚至建立了一个函数叫 **interact**。`interact` 接受一个 `String -> String` 的函数，并回传一个 I/O action。那个 I/O action 会读取一些输入，调用提供的函数，然后把函数的结果打印出来。所以我们的程序可以改写成这样。

```haskell
main = interact shortLinesOnly

shortLinesOnly :: String -> String
shortLinesOnly input =
    let allLines = lines input
        shortLines = filter (\line -> length line < 10) allLines
        result = unlines shortLines
    in result
```

我们甚至可以再让代码更短一些，像这样

```haskell
main = interact $ unlines . filter ((<10) . length) . lines
```

看吧，我们让程序缩到只剩一行了，很酷吧！

能应用 `interact` 的情况有几种，像是从输入 pipe 读进一些内容，然后丢出一些结果的程序；或是从用户获取一行一行的输入，然后丢回根据那一行运算的结果，再拿取另一行。这两者的差别主要是取决于用户使用他们的方式。

我们再来写另一个程序，它不断地读取一行行并告诉我们那一行字串是不是一个回文本串 (palindrome)。我们当然可以用 `getLine` 读取一行然后再调用 `main` 作同样的事。不过同样的事情可以用 `interact` 更简洁地达成。当使用 `interact` 的时候，想像你是将输入经有某些转换成输出。在这个情况当中，我们要将每一行输入转换成 `"palindrome"` 或 `"not a palindrome"`。所以我们必须写一个函数将 `"elephant\nABCBA\nwhatever"` 转换成 `not a palindrome\npalindrome\nnot a palindrome"`。来动手吧！

```haskell
respondPalindromes contents = unlines (map (\xs ->
    if isPalindrome xs then "palindrome" else "not a palindrome") (lines contents))
        where isPalindrome xs = xs == reverse xs
```

再来将程序改写成 point-free 的形式

```haskell
respondPalindromes = unlines . map (\xs ->
    if isPalindrome xs then "palindrome" else "not a palindrome") . lines
        where isPalindrome xs = xs == reverse xs
```

很直觉吧！首先将 `"elephant\nABCBA\nwhatever"` 变成 `["elephant", "ABCBA", "whatever"]` 然后将一个 lambda 函数 map 它，`["not a palindrome", "palindrome", "not a palindrome"]` 然后用 `unlines` 变成一行字串。接着

```haskell
main = interact respondPalindromes
```

来测试一下吧。

```haskell
$ runhaskell palindrome.hs
hehe
not a palindrome
ABCBA
palindrome
cookie
not a palindrome
```

即使我们的程序是把一大把字串转换成另一个，其实他表现得好像我们是一行一行做的。这是因为 Haskell 是惰性的，程序想要打印出第一行结果时，他必须要先有第一行输入。所以一旦我们给了第一行输入，他便打印出第一行结果。我们用 end-of-line 字符来结束程序。

我们也可以用 pipe 的方式将输入喂给程序。假设我们有这样一个文件。

```haskell
dogaroo
radar
rotor
madam
```

将他存为 `words.txt`，将他喂给程序后得到的结果

```haskell
$ cat words.txt | runhaskell palindromes.hs
not a palindrome
palindrome
palindrome
palindrome
```

再一次地提醒，我们得到的结果跟我们自己一个一个字打进输入的内容是一样的。我们看不到 `palindrome.hs` 输入的内容是因为内容来自于文件。

你应该大致了解 Lazy I/O 是如何运作，并能善用他的优点。他可以从输入转换成输出的角度方向思考。由于 Lazy I/O，没有输入在被用到之前是真的被读入。

到目前为止，我们的示范都是从终端读取某些东西或是打印出某些东西到终端。但如果我们想要读写文件呢？其实从某个角度来说我们已经作过这件事了。我们可以把读写终端想成读写文件。只是把文件命名成 `stdout` 跟 `stdin` 而已。他们分别代表标准输出跟标准输入。我们即将看到的读写文件跟读写终端并没什么不同。

首先来写一个程序，他会开启一个叫 girlfriend.txt 的文件，文件里面有 Avril Lavigne 的畅销名曲 Girlfriend，并将内容打印到终端上。接下来是 girlfriend.txt 的内容。

```haskell
Hey! Hey! You! You!
I don't like your girlfriend!
No way! No way!
I think you need a new one!
```

这则是我们的主程序。

```haskell
import System.IO

main = do
    handle <- openFile "girlfriend.txt" ReadMode
    contents <- hGetContents handle
    putStr contents
    hClose handle
```

执行他后得到的结果。

```haskell
$ runhaskell girlfriend.hs
Hey! Hey! You! You!
I don't like your girlfriend!
No way! No way!
I think you need a new one!
```

我们来一行行看一下程序。我们的程序用 do 把好几个 I/O action 绑在一起。在 do block 的第一行，我们注意到有一个新的函数叫 **openFile**。他的 type signature 是 `openFile :: FilePath -> IOMode -> IO Handle`。他说了 `openFile` 接受一个文件路径跟一个 `IOMode`，并回传一个 I/O action，他会打开一个文件并把文件关联到一个 handle。

`FilePath` 不过是 `String` 的 type synonym。

```haskell
type FilePath = String
```

`IOMode` 则是一个定义如下的型态

```haskell
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
```

![](/files/-LjUmt7BshmjkYCXzMll)

就像我们之前定义的型态，分别代表一个星期的七天。这个型态代表了我们想对打开的文件做什么。很简单吧。留意到我们的型态是 `IOMode` 而不是 `IO Mode`。`IO Mode` 代表的是一个 I/O action 包含了一个型态为 `Mode` 的值，但 `IOMode` 不过是一个阳春的 enumeration。

最后，他回传一个 I/O action 会将指定的文件用指定的模式打开。如果我们将 I/O action 绑定到某个东西，我们会得到一个 `Handle`。型态为 `Handle` 的值代表我们的文件在哪里。有了 handle 我们才知道要从哪个文件读取内容。想读取文件但不将文件绑定到 handle 上这样做是很蠢的。所以，我们将一个 handle 绑定到 `handle`。

接着一行，我们看到一个叫 **hGetContents** 的函数。他接了一个 `Handle`，所以他知道要从哪个文件读取内容并回传一个 `IO String`。一个包含了文件内容的 I/O action。这函数跟 `getContents` 差不多。唯一的差别是 `getContents` 会自动从标准输入读取内容（也就是终端），而 `hGetContents` 接了一个 file handle，这 file handle 告诉他读取哪个文件。除此之外，他们都是一样的。就像 `getContents`，`hGetContents` 不会把文件一次都拉到内存中，而是有必要才会读取。这非常酷，因为我们把 `contents` 当作是整个文件般用，但他实际上不在内存中。就算这是个很大的文件，`hGetContents` 也不会塞爆你的内存，而是只有必要的时候才会读取。

要留意文件的 handle 还有文件的内容两个概念的差异，在我们的程序中他们分别被绑定到 `handle` 跟 `contents` 两个名字。handle 是我们拿来区分文件的依据。如果你把整个文件系统想成一本厚厚的书，每个文件分别是其中的一个章节，handle 就像是书签一般标记了你现在正在阅读（或写入）哪一个章节，而内容则是章节本身。

我们使用 `putStr contents` 打印出内容到标准输出，然后我们用了 **hClose**。他接受一个 handle 然后回传一个关掉文件的 I/O action。在用了 `openFile` 之后，你必须自己把文件关掉。

要达到我们目的的另一种方式是使用 **withFile**，他的 type signature 是 `withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a`。他接受一个文件路径，一个 `IOMode` 以及一个函数，这函数则接受一个 handle 跟一个 I/O action。`withFile` 最后回传一个会打开文件，对文件作某件事然后关掉文件的 I/O action。处理的结果是包在最后的 I/O action 中，这结果跟我们给的函数的回传是相同的。这听起来有些复杂，但其实很简单，特别是我们有 lambda，来看看我们用 `withFile` 改写前面程序的一个范例：

```haskell
import System.IO

main = do
    withFile "girlfriend.txt" ReadMode (\handle -> do
            contents <- hGetContents handle
            putStr contents)
```

正如你看到的，程序跟之前的看起来很像。`(\handle -> ... )` 是一个接受 handle 并回传 I/O action 的函数，他通常都是用 lambda 来表示。我们需要一个回传 I/O action 的函数的理由而不是一个本身作处理并关掉文件的 I/O action，是因为这样一来那个 I/O action 不会知道他是对哪个文件在做处理。用 `withFile` 的话，`withFile` 会打开文件并把 handle 传给我们给他的函数，之后他则拿到一个 I/O action，然后作成一个我们描述的 I/O action，最后关上文件。例如我们可以这样自己作一个 `withFile`：

```haskell
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
    handle <- openFile path mode
    result <- f handle
    hClose handle
    return result
```

![](/files/-MlVFmxymxGK2F2tyDU0)

我们知道要回传的是一个 I/O action，所以我们先放一个 do。首先我们打开文件，得到一个 handle。然后我们 apply `handle` 到我们的函数，并得到一个做事的 I/O action。我们绑定那个 I/O action 到 `result` 这个名字，关上 handle 并 `return result`。`return` 的作用把从 `f` 得到的结果包在 I/O action 中，这样一来 I/O action 中就包含了 `f handle` 得到的结果。如果 `f handle` 回传一个从标准输入读去数行并写到文件然后回传读入的行数的 I/O action，在 `withFile'` 的情形中，最后的 I/O action 就会包含读入的行数。

就像 `hGetContents` 对应 `getContents` 一样，只不过是针对某个文件。我们也有 **hGetLine**、**hPutStr**、**hPutStrLn**、**hGetChar** 等等。他们分别是少了 h 的那些函数的对应。只不过他们要多拿一个 handle 当参数，并且是针对特定文件而不是标准输出或标准输入。像是 `putStrLn` 是一个接受一个字串并回传一个打印出加了换行字符的字串的 I/O action 的函数。`hPutStrLn` 接受一个 handle 跟一个字串，回传一个打印出加了换行字符的字串到文件的 I/O action。以此类推，`hGetLine` 接受一个 handle 然后回传一个从文件读取一行的 I/O action。

读取文件并对他们的字串内容作些处理实在太常见了，常见到我们有三个函数来更进一步简化我们的工作。

**readFile** 的 type signature 是 `readFile :: FilePath -> IO String`。记住，`FilePath` 不过是 `String` 的一个别名。`readFile` 接受一个文件路径，回传一个惰性读取我们文件的 I/O action。然后将文件的内容绑定到某个字串。他比起先 `openFile`，绑定 handle，然后 `hGetContents` 要好用多了。这边是一个用 `readFile` 改写之前例子的范例：

```haskell
import System.IO

main = do
    contents <- readFile "girlfriend.txt"
    putStr contents
```

由于我们拿不到 handle，所以我们也无法关掉他。这件事 Haskell 的 `readFile` 在背后帮我们做了。

**writeFile** 的型态是 `writefile :: FilePath -> String -> IO ()`。他接受一个文件路径，以及一个要写到文件中的字串，并回传一个写入动作的 I/O action。如果这个文件已经存在了，他会先把文件内容都砍了再写入。下面示范了如何把 girlfriend.txt 的内容转成大写然后写入到 girlfriendcaps.txt 中

```haskell
import System.IO
import Data.Char

main = do
    contents <- readFile "girlfriend.txt"
    writeFile "girlfriendcaps.txt" (map toUpper contents)
```

```haskell
$ runhaskell girlfriendtocaps.hs
$ cat girlfriendcaps.txt
HEY! HEY! YOU! YOU!
I DON'T LIKE YOUR GIRLFRIEND!
NO WAY! NO WAY!
I THINK YOU NEED A NEW ONE!
```

**appendFile** 的型态很像 `writeFile`，只是 `appendFile` 并不会在文件存在时把文件内容砍掉而是接在后面。

假设我们有一个文件叫 todo.txt\`\`，里面每一行是一件要做的事情。现在我们写一个程序，从标准输入接受一行将他加到我们的 to-do list 中。

```haskell
import System.IO

main = do
    todoItem <- getLine
    appendFile "todo.txt" (todoItem ++ "\n")
```

```haskell
$ runhaskell appendtodo.hs
Iron the dishes
$ runhaskell appendtodo.hs
Dust the dog
$ runhaskell appendtodo.hs
Take salad out of the oven
$ cat todo.txt
Iron the dishes
Dust the dog
Take salad out of the oven
```

由于 `getLine` 回传的值不会有换行字符，我们需要在每一行最后加上 `"\n"`。

还有一件事，我们提到 `contents <- hGetContents handle` 是惰性 I/O，不会将文件一次都读到内存中。 所以像这样写的话：

```haskell
main = do
    withFile "something.txt" ReadMode (\handle -> do
        contents <- hGetContents handle
        putStr contents)
```

实际上像是用一个 pipe 把文件弄到标准输出。正如你可以把 list 想成 stream 一样，你也可以把文件想成 stream。他会每次读一行然后打印到终端上。你也许会问这个 pipe 究竟一次可以塞多少东西，读去硬盘的频率究竟是多少？对于文本档而言，缺省的 buffer 通常是 line-buffering。这代表一次被读进来的大小是一行。这也是为什么在这个 case 我们是一行一行处理。对于 binary file 而言，缺省的 buffer 是 block-buffering。这代表我们是一个 chunk 一个 chunk 去读得。而一个 chunk 的大小是根据操作系统不同而不同。

你能用 `hSetBuffering` 来控制 buffer 的行为。他接受一个 handle 跟一个 `BufferMode`，回传一个会设置 buffer 行为的 I/O action。`BufferMode` 是一个 enumeration 型态，他可能的值有：`NoBuffering`, `LineBuffering` 或 `BlockBuffering (Maybe Int)`。其中 `Maybe Int` 是表示一个 chunck 有几个 byte。如果他的值是 `Nothing`，则操作系统会帮你决定 chunk 的大小。`NoBuffering` 代表我们一次读一个 character。一般来说 `NoBuffering` 的表现很差，因为他访问硬盘的频率很高。

接下来是我们把之前的范例改写成用 2048 bytes 的 chunk 读取，而不是一行一行读。

```haskell
main = do
    withFile "something.txt" ReadMode (\handle -> do
        hSetBuffering handle $ BlockBuffering (Just 2048)
        contents <- hGetContents handle
        putStr contents)
```

用更大的 chunk 来读取对于减少访问硬盘的次数是有帮助的，特别是我们的文件其实是透过网络来访问。

我们也可以使用 **hFlush**，他接受一个 handle 并回传一个会 flush buffer 到文件的 I/O action。当我们使用 line-buffering 的时候，buffer 在每一行都会被 flush 到文件。当我们使用 block-buffering 的时候，是在我们读每一个 chunk 作 flush 的动作。flush 也会发生在关闭 handle 的时候。这代表当我们碰到换行字符的时候，读或写的动作都会停止并回报手边的数据。但我们能使用 `hFlush` 来强迫回报所有已经在 buffer 中的数据。经过 flushing 之后，数据也就能被其他程序看见。

把 block-buffering 的读取想成这样：你的马桶会在水箱有一加仑的水的时候自动冲水。所以你不断灌水进去直到一加仑，马桶就会自动冲水，在水里面的数据也就会被看到。但你也可以手动地按下冲水钮来冲水。他会让现有的水被冲走。冲水这个动作就是 `hFlush` 这个名字的含意。

我们已经写了一个将 item 加进 to-do list 里面的程序，现在我们想加进移除 item 的功能。我先把代码粘贴然后讲解他。我们会使用一些新面孔像是 `System.Directory` 以及 `System.IO` 里面的函数。

来看一下我们包含移除功能的程序:

```haskell
import System.IO
import System.Directory
import Data.List

main = do
    handle <- openFile "todo.txt" ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let todoTasks = lines contents
    let numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStrLn "These are your TO-DO items:"
    putStr $ unlines numberedTasks
    putStrLn "Which one do you want to delete?"
    numberString <- getLine
    let number = read numberString
    let newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile "todo.txt"
    renameFile tempName "todo.txt"
```

一开始，我们用 read mode 打开 todo.txt，并把他绑定到 `handle`。

接着，我们使用了一个之前没用过在 `System.IO` 中的函数 **openTempFile**。他的名字浅显易懂。他接受一个暂存的文件夹跟一个样板文件名，然后打开一个暂存盘。我们使用 `"."` 当作我们的暂存文件夹，因为 `.` 在几乎任何操作系统中都代表了现在所在的文件夹。我们使用 `"temp"` 当作我们暂存盘的样板名，他代表暂存盘的名字会是 temp 接上某串随机字串。他回传一个创建暂存盘的 I/O action，然后那个 I/O action 的结果是一个 pair：暂存盘的名字跟一个 handle。我们当然可以随便开启一个 todo2.txt 这种名字的文件。但使用 `openTempFile` 会是比较好的作法，这样你不会不小心覆写任何文件。

我们不用 `getCurrentDirectory` 的来拿到现在所在文件夹而用 `"."` 的原因是 `.` 在 unix-like 系统跟 Windows 中都表示现在的文件夹。

然后，我们绑定 todo.txt 的内容成 `contents`。把字串断成一串字串，每个字串代表一行。`todoTasks` 就变成 `["Iron the dishes", "Dust the dog", "Take salad out of the oven"]`。我们用一个会把 3 跟 `"hey"` 变成 `"3 - hey"` 的函数，然后从 0 开始把这个串列 zip 起来。所以 `numberedTasks` 就是 `["0 - Iron the dishes", "1 - Dust the dog" ...`。我们用 `unlines` 把这个串列变成一行，然后打印到终端上。注意我们也有另一种作法，就是用 `mapM putStrLn numberedTasks`。

我们问用户他们想要删除哪一个并且等着他们输入一个数字。假设他们想要删除 1 号，那代表 `Dust the dog`，所以他们输入 `1`。于是 `numberString` 就代表 `"1"`。由于我们想要一个数字，而不是一个字串，所以我们用对 `1` 使用 `read`，并且绑定到 `number`。

还记得在 `Data.List` 中的 `delete` 跟 `!!` 吗？`!!` 回传某个 index 的元素，而 `delete` 删除在串列中第一个发现的元素，然后回传一个新的没有那个元素的串列。`(todoTasks !! number)` （number 代表 `1`） 回传 `"Dust the dog"`。我们把 `todoTasks` 去掉第一个 `"Dust the dog"` 后的串列绑定到 `newTodoItems`，然后用 `unlines` 变成一行然后写到我们所打开的暂存盘。旧有的文件并没有变动，而暂存盘包含砍掉那一行后的所有内容。

在我们关掉源文件跟暂存盘之后我们用 **removeFile** 来移除原本的文件。他接受一个文件路径并且删除文件。删除旧得 todo.txt 之后，我们用 **renameFile** 来将暂存盘重命名成 todo.txt。特别留意 `removeFile` 跟 `renameFile`（两个都在 `System.Directory` 中）接受的是文件路径，而不是 handle。

这就是我们要的，实际上我们可以用更少行写出同样的程序，但我们很小心地避免覆写任何文件，并询问操作系统我们可以把暂存盘摆在哪？让我们来执行看看。

```haskell
$ runhaskell deletetodo.hs
These are your TO-DO items:
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
Which one do you want to delete?
1

$ cat todo.txt
Iron the dishes
Take salad out of the oven

$ runhaskell deletetodo.hs
These are your TO-DO items:
0 - Iron the dishes
1 - Take salad out of the oven
Which one do you want to delete?
0

$ cat todo.txt
Take salad out of the oven
```

## 命令行引数

![](/files/-MlVFmy0vj-CuIe2MNMn)

如果你想要写一个在终端里运行的程序，处理命令行引数是不可或缺的。幸运的是，利用 Haskell 的 Standard Libary 能让我们有效地处理命令行引数。

在之前的章节中，我们写了一个能将 to-do item 加进或移除 to-do list 的一个程序。但我们的写法有两个问题。第一个是我们把放 to-do list 的文件名称给写死了。我们擅自决定用户不会有很多个 to-do lists，就把文件命名为 todo.txt。

一种解决的方法是每次都询问用户他们想将他们的 to-do list 放进哪个文件。我们在用户要删除的时候也采用这种方式。这是一种可以运作的方式，但不太能被接受，因为他需要用户运行程序，等待程序询问才能回答。这被称为交互式的程序，但讨厌的地方在当你想要自动化执行程序的时候，好比说写成 script，这会让你的 script 写起来比较困难。

这也是为什么有时候让用户在执行的时候就告诉程序他们要什么会比较好，而不是让程序去问用户要什么。比较好的方式是让用户透过命令行引数告诉程序他们想要什么。

在 `System.Environment` 模块当中有两个很酷的 I/O actions，一个是 **getArgs**，他的 type 是 `getArgs :: IO [String]`，他是一个拿取命令行引数的 I/O action，并把结果放在包含的一个串列中。**getProgName** 的型态是 `getProgName :: IO String`，他则是一个 I/O action 包含了程序的名称。

我们来看一个展现他们功能的程序。

```haskell
import System.Environment
import Data.List

main = do
    args <- getArgs
    progName <- getProgName
    putStrLn "The arguments are:"
    mapM putStrLn args
    putStrLn "The program name is:"
    putStrLn progName
```

我们将 `getArgs` 跟 `progName` 分别绑定到 `args` 跟 `progName`。我们打印出 `The arguments are:` 以及在 `args` 中的每个引数。最后，我们打印出程序的名字。我们把程序编译成 `arg-test`。

```haskell
$ ./arg-test first second w00t "multi word arg"
The arguments are:
first
second
w00t
multi word arg
The program name is:
arg-test
```

知道了这些函数现在你能写几个很酷的命令行程序。在之前的章节，我们写了一个程序来加入待作事项，也写了另一个程序删除事项。现在我们要把两个程序合起来，他会根据命令行引数来决定该做的事情。我们也会让程序可以处理不同的文件，而不是只有 todo.txt

我们叫这程序 todo，他会作三件事：

```
# 查看待作事项
# 加入待作事项
# 删除待作事项
```

我们暂不考虑不合法的输入这件事。

我们的程序要像这样运作：假如我们要加入 `Find the magic sword of power`，则我们会打 `todo add todo.txt "Find the magic sword of power"`。要查看事项我们则会打 `todo view todo.txt`，如果要移除事项二则会打 `todo remove todo.txt 2`

我们先作一个分发的 association list。他会把命令行引数当作 key，而对应的处理函数当作 value。这些函数的型态都是 `[String] -> IO ()`。他们会接受命令行引数的串列并回传对应的查看，加入以及删除的 I/O action。

```haskell
import System.Environment
import System.Directory
import System.IO
import Data.List

dispatch :: [(String, [String] -> IO ())]
dispatch =  [ ("add", add)
            , ("view", view)
            , ("remove", remove)
            ]
```

我们定义了 `main`，`add`，`view` 跟 `remove`，就从 `main` 开始讲吧：

```haskell
main = do
    (command:args) <- getArgs
    let (Just action) = lookup command dispatch
    action args
```

首先，我们取出引数并把他们绑定到 `(command:args)`。如果你还记得 pattern matching，这么做会把第一个引数绑定到 `command`，把其他的绑定到 `args`。如果我们像这样执行程序 `todo add todo.txt "Spank the monkey"`，`command` 会变成 `"add"`，而 `args` 会变成 `["todo.txt", "Spank the monkey"]`。

在下一行，我们在一个分派的串列中寻到我们的指令是哪个。由于 `"add"` 指向 `add`，我们的结果便是 `Just add`。我们再度使用了 pattern matching 来把我们的函数从 `Maybe` 中取出。但如果我们想要的指令不在分派的串列中呢？那样 lookup 就会回传 `Nothing`，但我们这边并不特别处理失败的情况，所以 pattern matching 会失败然后我们的程序就会当掉。

最后，我们用剩下的引数调用 `action` 这个函数。他会还传一个加入 item，显示所有 items 或者删除 item 的 I/O action。由于这个 I/O action 是在 `main` 的 do block 中，他最后会被执行。如果我们的 `action` 函数是 `add`，他就会被喂 `args` 然后回传一个加入 `Spank the monkey` 到 todo.txt 中的 I/O action。

我们剩下要做的就是实作 `add`，`view` 跟 `remove`，我们从 `add` 开始：

```haskell
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
```

如果我们这样执行程序 `todo add todo.txt "Spank the monkey"`，则 `"add"` 会被绑定到 `command`，而 `["todo.txt", "Spank the monkey"]` 会被带到从 dispatch list 中拿到的函数。

由于我们不处理不合法的输入，我们只针对这两项作 pattern matching，然后回传一个附加一行到文件末尾的 I/O action。

接着，我们来实作查看串列。如果我们想要查看所有 items，我们会 `todo view todo.txt`。所以 `command` 会是 `"view"`，而 `args` 会是 `["todo.txt"]`。

```haskell
view :: [String] -> IO ()
view [fileName] = do
    contents <- readFile fileName
    let todoTasks = lines contents
    numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStr $ unlines numberedTasks
```

这跟我们之前删除文件的程序差不多，只是我们是在显示内容而已，

最后，我们要来实作 `remove`。他基本上跟之前写的只有删除功能的程序很像，所以如果你不知道删除是怎么做的，可以去看之前的解释。主要的差别是我们不写死 todo.txt，而是从参数取得。我们也不会提示用户要删除哪一号的 item，而是从参数取得。

```haskell
remove :: [String] -> IO ()
remove [fileName, numberString] = do
    handle <- openFile fileName ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let number = read numberString
        todoTasks = lines contents
        newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile fileName
    renameFile tempName fileName
```

我们打开 `fileName` 的文件以及一个暂存。删除用户要我们删的那一行后，把文件内容写到暂存盘。砍掉原本的文件然后把暂存盘重命名成 `fileName`。

来看看完整的程序。

```haskell
import System.Environment
import System.Directory
import System.IO
import Data.List

dispatch :: [(String, [String] -> IO ())]
dispatch =  [ ("add", add)
            , ("view", view)
            , ("remove", remove)
            ]

main = do
    (command:args) <- getArgs
    let (Just action) = lookup command dispatch
    action args

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")

view :: [String] -> IO ()
view [fileName] = do
    contents <- readFile fileName
    let todoTasks = lines contents
        numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
    putStr $ unlines numberedTasks

remove :: [String] -> IO ()
remove [fileName, numberString] = do
    handle <- openFile fileName ReadMode
    (tempName, tempHandle) <- openTempFile "." "temp"
    contents <- hGetContents handle
    let number = read numberString
        todoTasks = lines contents
        newTodoItems = delete (todoTasks !! number) todoTasks
    hPutStr tempHandle $ unlines newTodoItems
    hClose handle
    hClose tempHandle
    removeFile fileName
    renameFile tempName fileName
```

![](/files/-MlVFmy7Yqz0cdGGSpcL)

总结我们的程序：我们做了一个 dispatch association，将指令对应到一些会接受命令行引数并回传 I/O action 的函数。我们知道用户下了什么命令，并根据那个命令从 dispatch list 取出对影的函数。我们用剩下的命令行引数调用哪些函数而得到一些作相对应事情的 I/O action。然后便执行那些 I/O action。

在其他编程语言，我们可能会用一个大的 switch case 来实作，但使用高端函数让我们可以要 dispatch list 给我们要的函数，并要那些函数给我们适当的 I/O action。

让我们看看执行结果。

```haskell
$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven

$ ./todo add todo.txt "Pick up children from drycleaners"

$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Take salad out of the oven
3 - Pick up children from drycleaners

$ ./todo remove todo.txt 2

$ ./todo view todo.txt
0 - Iron the dishes
1 - Dust the dog
2 - Pick up children from drycleaners
```

要再另外加新的选项也是很容易。只要在 dispatch list 加入新的会作你要的事情函数。你可以试试实作一个 `bump` 函数，接受一个文件跟一个 task number，他会回传一个把那个 task 搬到 to-do list 顶端的 I/O action。

对于不合法的输入你也可以让程序结束地漂亮一点。(例如用户输入了 `todo UP YOURS HAHAHAHA`)可以作一个回报错误的 I/O action (例如 \`\`errorExist :: IO ())检查有没有不合法的输入，如果有便执行这个回报错误的 I/O action。我们之后会谈另一个可能，就是用 exception。

## 乱数

![](/files/-LjUms6hwuwSTrAHuY4f)

在许多情况下，你写程序会需要些随机的数据。或许你在制作一个游戏，在游戏中你需要掷骰子。或是你需要测试程序的测试数据。精准一点地说，我们需要 pseudo-random 的数据，我们知道真正的随机数据好比是一只猴子拿着起司跟奶油骑在单轮车上，任何事情都会发生。在这个章节，我们要看看如何让 Haskell 产生些 pseudo-random 的数据。

在大多数其他的编程语言中，会给你一些函数能让你拿到些随机乱数。每调用一次他就会拿到一个不同的数字。那在 Haskell 中是如何？要记住 Haskell 是一个纯粹函数式语言。代表任何东西都具有 referential transparency。那代表你喂给一个函数相同的参数，不管怎么调用都是回传相同的结果。这很新奇的原因是因为他让我们理解程序的方式不同，而且可以让我们延迟计算，直到我们真正需要他。如果我调用一个函数，我可以确定他不会乱来。我真正在乎的是他的结果。然而，这会造成在乱数的情况有点复杂。如果我有一个函数像这样：

```haskell
randomNumber :: (Num a) => a
randomNumber = 4
```

由于他永远回传 `4`，所以对于乱数的情形而言是没什么意义。就算 4 这个结果是掷骰子来的也没有意义。

其他的编程语言是怎么产生乱数的呢？他们可能随便拿取一些电脑的信息，像是现在的时间，你怎么移动你的鼠标，以及周围的声音。根据这些算出一个数值让他看起来好像随机的。那些要素算出来的结果可能在每个时间都不同，所以你会拿到不同的随机数字。

所以说在 Haskell 中，假如我们能作一个函数，他会接受一个具随机性的参数，然后根据那些信息还传一个数值。

在 `System.Random` 模块中。他包含所有满足我们需求的函数。让我们先来看其中一个，就是 **random**。他的型态是 `random :: (RandomGen g, Random a) => g -> (a, g)`。哇，出现了新的 typeclass。**RandomGen** typeclass 是指那些可以当作乱源的型态。而**Random** typeclass 则是可以装乱数的型态。一个布尔值可以是随机值，不是 `True` 就是 `False`。一个整数可以是随机的好多不同值。那你会问，函数可以是一个随机值吗？我不这么认为。如果我们试着翻译 `random` 的型态宣告，大概会是这样：他接受一个 random generator (乱源所在)，然后回传一个随机值以及一个新的 random generator。为什么他要回传一个新的 random generator 呢？就是下面我们要讲的。

要使用 `random` 函数， 我们必须要了解 random generator。 在 `System.Random` 中有一个很酷的型态，叫做 **StdGen**， 他是 `RandomGen` 的一个 instance。 我们可以自己手动作一个 `StdGen` 也可以告诉系统给我们一个现成的。

要自己做一个 random generator，要使用 **mkStdGen** 这个函数。他的型态是 `mkStdGen :: Int -> StdGen`。他接受一个整数，然后根据这个整数会给一个 random generator。让我们来试一下 `random` 以及 `mkStdGen`，用他们产生一个乱数吧。

```haskell
ghci> random (mkStdGen 100)
```

```haskell
<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
        `Random a' arising from a use of `random' at <interactive>:1:0-20
    Probable fix: add a type signature that fixes these type variable(s)  `
```

这是什么？由于 `random` 函数会回传 `Random` typeclass 中任何一种型态，所以我们必须告诉 Haskell 我们是要哪一种型态。不要忘了我们是回传 random value 跟 random generator 的一个 pair

```haskell
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)
```

我们终于有了一个看起来像乱数的数字。tuple 的第一个部份是我们的乱数，而第二个部份是一个新的 random generator 的文本表示。如果我们用相同的 random generator 再调用 `random` 一遍呢？

```haskell
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)
```

不易外地我们得到相同的结果。所以我们试试用不同的 random generator 作为我们的参数。

```haskell
ghci> random (mkStdGen 949494) :: (Int, StdGen)
(539963926,466647808 1655838864)
```

很好，我们拿到了不同的数字。我们可以用不同的型态标志来拿到不同型态的乱数

```haskell
ghci> random (mkStdGen 949488) :: (Float, StdGen)
(0.8938442,1597344447 1655838864)
ghci> random (mkStdGen 949488) :: (Bool, StdGen)
(False,1485632275 40692)
ghci> random (mkStdGen 949488) :: (Integer, StdGen)
(1691547873,1597344447 1655838864)
```

让我们写一个仿真丢三次铜板的函数。假如 `random` 不同时回传一个乱数以及一个新的 random generator，我们就必须让这函数接受三个 random generators 让他们每个回传一个掷铜板的结果。但那样听起来怪怪的，加入一个 generator 可以产生一个型态是 `Int` 的乱数，他应该可以产生掷三次铜板的结果（总共才八个组合）。这就是 `random` 为什么要回传一个新的 generator 的关键了。

我们将一个铜板表示成 `Bool`。`True` 代表反面，`False` 代表正面。

```haskell
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen
    (secondCoin, newGen') = random newGen
    (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)
```

我们用我们拿来当参数的 generator 调用 `random` 并得到一个掷铜板的结果跟一个新的 generator。然后我们再用新的 generator 调用他一遍，来得到第二个掷铜板的结果。对于第三个掷铜板的结果也是如法炮制。如果我们一直都用同样的 generator，那所有的结果都会是相同的值。也就是不是 `(False, False, False)` 就是 `(True, True, True)`。

```haskell
ghci> threeCoins (mkStdGen 21)
(True,True,True)
ghci> threeCoins (mkStdGen 22)
(True,False,True)
ghci> threeCoins (mkStdGen 943)
(True,False,True)
ghci> threeCoins (mkStdGen 944)
(True,True,True)
```

留意我们不需要写 `random gen :: (Bool, StdGen)`。那是因为我们已经在函数的型态宣告那边就表明我们要的是布林。而 Haskell 可以推敲出我们要的是布尔值。

假如我们要的是掷四次？甚至五次呢？有一个函数叫 **randoms**，他接受一个 generator 并回传一个无穷串行。

```haskell
ghci> take 5 $ randoms (mkStdGen 11) :: [Int]
[-1807975507,545074951,-1015194702,-1622477312,-502893664]
ghci> take 5 $ randoms (mkStdGen 11) :: [Bool]
[True,True,True,True,False]
ghci> take 5 $ randoms (mkStdGen 11) :: [Float]
[7.904789e-2,0.62691015,0.26363158,0.12223756,0.38291094]
```

为什么 `randoms` 不另外多回传一个新的 generator 呢？我们可以这样地实作 `randoms`

```haskell
randoms' :: (RandomGen g, Random a) => g -> [a]
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen
```

一个递归的定义。我们由现在的 generator 拿到一个乱数跟一个新的 generator，然后制作一个 list，list 的第一个值是那个乱数，而 list 的其余部份是根据新的 generator 产生出的其余乱数们。由于我们可能产生出无限的乱数，所以不可能回传一个新的 generator。

我们可以写一个函数，他会回传有限个乱数跟一个新的 generator

```haskell
finiteRandoms :: (RandomGen g, Random a, Num n, Eq n) => n -> g -> ([a], g)
finiteRandoms 0 gen = ([], gen)
finiteRandoms n gen =
    let (value, newGen) = random gen
        (restOfList, finalGen) = finiteRandoms (n-1) newGen
    in  (value:restOfList, finalGen)
```

又是一个递归的定义。我们说如果我们要 0 个乱数，我们便回传一个空的 list 跟原本给我们的 generator。对于其他数量的乱数，我们先拿一个乱数跟一个新的 generator。这一个乱数便是 list 的第一个数字。然后 list 中剩下的便是 n-1 个由新的 generator 产生出的乱数。然后我们回传整个 list 跟最后一个产生完 n-1 个乱数后 generator。

如果我们要的是在某个范围内的乱数呢？现在拿到的乱数要不是太大就是太小。如果我们想要的是骰子上的数字呢？**randomR** 能满足我们的需求。他的型态是 `randomR :: (RandomGen g, Random a) :: (a, a) -> g -> (a, g)`，代表他有点类似 `random`。只不过他的第一个参数是一对数目，定义了最后产生乱数的上界以及下界。

```haskell
ghci> randomR (1,6) (mkStdGen 359353)
(6,1494289578 40692)
ghci> randomR (1,6) (mkStdGen 35935335)
(3,1250031057 40692)
```

另外也有一个 **randomRs** 的函数，他会产生一连串在给定范围内的乱数：

```haskell
ghci> take 10 $ randomRs ('a','z') (mkStdGen 3) :: [Char]
"ndkxbvmomg"
```

这结果看起来像是一个安全性很好的密码。

你会问你自己，这一单元跟 I/O 有关系吗？到现在为止还没出现任何跟 I/O 有关的东西。到现在为止我们都是手动地做我们的 random generator。但那样的问题是，程序永远都会回传同样的乱数。这在真实世界中的程序是不能接受的。这也是为什么 `System.Random` 要提供 **getStdGen** 这个 I/O action，他的型态是 `IO StdGen`。当你的程序执行时，他会跟系统要一个 random generator，并存成一个 global generator。`getStdGen` 会替你拿那个 global random generator 并把他绑定到某个名称上。

这里有一个简单的产生随机字串的程序。

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStr $ take 20 (randomRs ('a','z') gen)
```

```haskell
$ runhaskell random_string.hs
pybphhzzhuepknbykxhe
$ runhaskell random_string.hs
eiqgcxykivpudlsvvjpg
$ runhaskell random_string.hs
nzdceoconysdgcyqjruo
$ runhaskell random_string.hs
bakzhnnuzrkgvesqplrx
```

要当心当我们连续两次调用 `getStdGent` 的时候，实际上都会回传同样的 global generator。像这样：

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStrLn $ take 20 (randomRs ('a','z') gen)
    gen2 <- getStdGen
    putStr $ take 20 (randomRs ('a','z') gen2)
```

你会打印出两次同样的字串。要能得到两个不同的字串是建立一个无限的 stream，然后拿前 20 个字当作第一个字串，拿下 20 个字当作第二个字串。要这么做，我们需要在 `Data.List` 中的 `splitAt` 函数。他会把一个 list 根据给定的 index 切成一个 tuple，tuple 的第一部份就是切断的前半，第二个部份就是切断的后半。

```haskell
import System.Random
import Data.List

main = do
    gen <- getStdGen
    let randomChars = randomRs ('a','z') gen
        (first20, rest) = splitAt 20 randomChars
        (second20, _) = splitAt 20 rest
    putStrLn first20
    putStr second20
```

另一种方法是用 **newStdGen** 这个 I/O action，他会把现有的 random generator 分成两个新的 generators。然后会把其中一个指定成 global generator，并回传另一个。

```haskell
import System.Random

main = do
    gen <- getStdGen
    putStrLn $ take 20 (randomRs ('a','z') gen)
    gen' <- newStdGen
    putStr $ take 20 (randomRs ('a','z') gen')
```

当我们绑定 `newStdGen` 的时候我们不只是会拿到一个新的 generator，global generator 也会被重新指定。所以再调用一次 `getStdGen` 并绑定到某个名称的话，我们就会拿到跟 `gen` 不一样的 generator。

这边有一个小程序会让用户猜数字：

```haskell
import System.Random
import Control.Monad(when)

main = do
    gen <- getStdGen
    askForNumber gen

askForNumber :: StdGen -> IO ()
askForNumber gen = do
    let (randNumber, newGen) = randomR (1,10) gen :: (Int, StdGen)
    putStr "Which number in the range from 1 to 10 am I thinking of? "
    numberString <- getLine
    when (not $ null numberString) $ do
        let number = read numberString
        if randNumber == number
            then putStrLn "You are correct!"
            else putStrLn $ "Sorry, it was " ++ show randNumber
            askForNumber newGen
```

![](/files/-MlVFmyCmSwpO7oAXX5Z)

我们写了一个 `askForNumber` 的函数，他接受一个 random generator 并回传一个问用户要数字并回答是否正确的 I/O action。在那个函数里面，我们先根据从参数拿到的 generator 产生一个乱数以及一个新的 generator，分别叫他们为 `randomNumber` 跟 `newGen`。假设那个产生的数字是 `7`。则我们要求用户猜我们握有的数字是什么。我们用 `getLine` 来将结果绑定到 `numberString` 上。当用户输入 `7`，`numberString` 就会是 `"7"`。接下来，我们用 `when` 来检查用户输入的是否是空字串。如果是，那一个空的 I/O action `return ()` 就会被回传。基本上就等于是结束程序的意思。如果不是，那 I/O action 就会被执行。我们用 `read` 来把 `numberString` 转成一个数字，所以 `number` 便会是 `7`。

```
如果用户给我们一些 ``read`` 没办法读取的输入（像是 ``"haha"``），我们的程序便会当掉并打印出错误消息。 如果你不希望你的程序当掉，就用 **reads**，当读取失败的时候他会回传一个空的 list。当成功的时候他就回传一个 tuple，第一个部份是我们想要的数字，第二个部份是读取失败的字串。
```

我们检查如果输入的数字跟我们随机产生的数字一样，便提示用户恰当的消息。然后再递归地调用 `askForNumber`，只是会拿到一个新的 generator。就像之前的 generator 一样，他会给我们一个新的 I/O action。

`main` 的组成很简单，就是由拿取一个 random generator 跟调用 `askForNumber` 组成罢了。

来看看我们的程序：

```haskell
$ runhaskell guess_the_number.hs
Which number in the range from 1 to 10 am I thinking of? 4
Sorry, it was 3
Which number in the range from 1 to 10 am I thinking of? 10
You are correct!
Which number in the range from 1 to 10 am I thinking of? 2
Sorry, it was 4
Which number in the range from 1 to 10 am I thinking of? 5
Sorry, it was 10
Which number in the range from 1 to 10 am I thinking of?
```

用另一种方式写的话像这样：

```haskell
import System.Random
import Control.Monad(when)

main = do
    gen <- getStdGen
    let (randNumber, _) = randomR (1,10) gen :: (Int, StdGen)
    putStr "Which number in the range from 1 to 10 am I thinking of? "
    numberString <- getLine
    when (not $ null numberString) $ do
        let number = read numberString
        if randNumber == number
            then putStrLn "You are correct!"
            else putStrLn $ "Sorry, it was " ++ show randNumber
        newStdGen
        main
```

他非常类似我们之前的版本，只是不是递归地调用，而是把所有的工作都在 `main` 里面做掉。在告诉用户他们猜得是否正确之后，便更新 global generator 然后再一次调用 `main`。两种策略都是有效但我比较喜欢第一种方式。因为他在 `main` 里面做的事比较少，并提供我们一个可以重复使用的函数。

## Bytestrings

![](/files/-MlVFmyECcczR-5cPcMK)

List 是一种有用又酷的数据结构。到目前为止，我们几乎无处不使用他。有好几个函数是专门处理 List 的，而 Haskell 惰性的性质又让我们可以用 filter 跟 map 来替换其他语言中的 for loop 跟 while loop。也由于 evaluation 只会发生在需要的时候，像 infinite list 也对于 Haskell 不成问题（甚至是 infinite list of infinite list）。这也是为什么 list 能被用来表达 stream，像是读取标准输入或是读取文件。我们可以打开文件然后读取内容成字串，即便实际上我们是需要的时候才会真正取读取。

然而，用字串来处理文件有一个缺点：就是他很慢。就像你所知道的，`String` 是一个 `[Char]` 的 type synonym。`Char` 没有一个固定的大小，因为他可能由好几个 byte 组成，好比说 Unicode。再加上 list 是惰性的。如果你有一个 list 像 `[1,2,3,4]`，他只会在需要的时候被 evaluate。所以整个 list 其实比较像是一个"保证"你会有一个 list。要记住 `[1,2,3,4]` 不过是 `1:2:3:4:[]` 的一个 syntactic sugar。当 list 的第一个元素被 evaluated 的时候，剩余的部份 `2:3:4:[]` 一样也只是一个"保证"你会有一个 list，以此类推。以此类推。以此类推。所以你可以想像成 list 是保证在你需要的时候会给你第一个元素，以及保证你会有剩下的部份当你还需要更多的时候。其实不难说服你这样做并不是一个最有效率的作法。

这样额外的负担在大多数时候不会造成困扰，但当我们要读取一个很大的文件的时候就是个问题了。这也是为什么 Haskell 要有 `bytestrings`。Bytestrings 有点像 list，但他每一个元素都是一个 byte (8 bits)，而且他们惰性的程度也是不同。

Bytestrings 有两种：strict 跟 lazy。Strict bytestrings 放在 `Data.ByteString`，他们把惰性的性质完全拿掉。不会有所谓任何的「保证」，一个 strict bytestring 就代表一连串的 bytes。因此你不会有一个无限长的 strict bytestrings。如果你 evaluate 第一个 byte，你就必须 evalute 整个 bytestring。这么做的优点是他会比较少 overhaed，因为他没有 "Thunk"（也就是用 Haskell 术语来说的「保证」）。缺点就是他可能会快速消耗你的内存，因为你把他们一次都读进了内存。

另一种 bytestring 是放在 `Data.ByteString.Lazy` 中。他们具有惰性，但又不像 list 那么极端。就像我们之前说的，List 的 thunk 个数是跟 list 中有几个元素一模一样。这也是为什么他们速度没办法满足一些特殊需求。Lazy bytestrings 则用另一种作法，他们被存在 chunks 中（不要跟 Thunk 搞混），每一个 chunk 的大小是 64K。所以如果你 evaluate lazy bytestring 中的 byte，则前 64K 会被 evaluated。在那个 chunck 之后，就是一些「保证」会有剩余的 chunk。lazy bytestrings 有点像装了一堆大小为 64K 的 strict bytestrings 的 list。当你用 lazy bytestring 处理一个文件的时候，他是一个 chunk 一个 chunk 去读。这很棒是因为他不会让我们一下使用大量的内存，而且 64K 有很高的可能性能够装进你 CPU 的 L2 Cache。

如果你大概看过 `Data.ByteString.Lazy` 的文档，你会看到到他有一堆函数的名称跟 `Data.List` 中的函数名称相同，只是出现的 type signature 是 `ByteString` 而不是 `[a]`，是 `Word8` 而不是 `a`。同样名称的函数基本上表现的行为跟 list 中的差不多。因为名称是一样的，所以必须用 qualified import 才不会在装载进 GHCI 的时候造成冲突。

```haskell
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as S
```

`B` 中有 lazy bytestrings 跟对应的函数，而 `S` 中则有 strict 的版本。大多数时候我们是用 lazy 的版本。

**pack** 函数的 type signature 是 `pack :: [Word8] -> ByteString`。代表他接受一串型态为 `Word8` 的 bytes，并回传一个 `ByteString`。你能想像一个 lazy 的 list，要让他稍微不 lazy 一些，所以让他对于 64K lazy。

那 `Word8` 型态又是怎么一回事？。他就像 `Int`，只是他的范围比较小，介于 0-255 之间。他代表一个 8-bit 的数字。就像 `Int` 一样，他是属于 `Num` 这个 typeclass。例如我们知道 `5` 是 polymorphic 的，他能够表现成任何数值型态。其实 `Word8` 他也能表示。

```haskell
ghci> B.pack [99,97,110]
Chunk "can" Empty
ghci> B.pack [98..120]
Chunk "bcdefghijklmnopqrstuvwx" Empty
```

正如你看到的，你其实不必特别在意 `Word8`，因为型态系统会选择正确的型态。如果你试着用比较大的数字，像是 `336`。那对于 `Word8` 他就会变成 `80`。

我们把一些数值打包成 `ByteString`，使他们可以塞进一个 chunk 里面。`Empty` 之于 `ByteString` 就像 `[]` 之于 list 一样。

**unpack** 是 `pack` 的相反，他把一个 bytestring 变成一个 byte list。

**fromChunks** 接受一串 strict 的 bytestrings 并把他变成一串 lazy bytestring。**toChunks** 接受一个 lazy bytestrings 并将他变成一串 strict bytestrings。

```haskell
ghci> B.fromChunks [S.pack [40,41,42], S.pack [43,44,45], S.pack [46,47,48]]
Chunk "()*" (Chunk "+,-" (Chunk "./0" Empty))
```

如果你有很多小的 strict bytestrings 而且不想先将他们 join 起来（会耗损 memory）这样的作法是不错的。

bytestring 版本的 `:` 叫做 **cons**。他接受一个 byte 跟一个 bytestring，并把这个 byte 放到 bytestring 的前端。他是 lazy 的操作，即使 bytestring 的第一个 chunk 不是满的，他也会添加一个 chunk。这也是为什么当你要插入很多 bytes 的时候最好用 strict 版本的 `cons`，也就是 **cons'**。

```haskell
ghci> B.cons 85 $ B.pack [80,81,82,84]
Chunk "U" (Chunk "PQRT" Empty)
ghci> B.cons' 85 $ B.pack [80,81,82,84]
Chunk "UPQRT" Empty
ghci> foldr B.cons B.empty [50..60]
Chunk "2" (Chunk "3" (Chunk "4" (Chunk "5" (Chunk "6" (Chunk "7" (Chunk "8" (Chunk "9" (Chunk ":" (Chunk ";" (Chunk "<"
Empty))))))))))
ghci> foldr B.cons' B.empty [50..60]
Chunk "23456789:;<" Empty
```

你可以看到 **empty** 制造了一个空的 bytestring。也注意到 `cons` 跟 `cons'` 的差异了吗？有了 `foldr`，我们逐步地把一串数字从右边开始，一个个放到 bytestring 的前头。当我们用 `cons`，我们则得到一个 byte 一个 chunk 的结果，并不是我们要的。

bytestring 模块有一大票很像 `Data.List` 中的函数。包括了 `head`，`tail`，`init`，`null`，`length`，`map`，`reverse`，`foldl`，`foldr`，`concat`，`takeWhile`，`filter`，等等。

他也有表现得跟 `System.IO` 中一样的函数，只有 `Strings` 被换成了 `ByteString` 而已。像是 `System.IO` 中的 `readFile`，他的型态是 `readFile :: FilePath -> IO String`，而 bytestring 模块中的 **readFile** 则是 `readFile :: FilePath -> IO ByteString`。小心，如果你用了 strict bytestring 来读取一个文件，他会把文件内容都读进内存中。而使用 lazy bytestring，他则会读取 chunks。

让我们来写一个简单的程序，他从命令行接受两个文件名，然后拷贝第一个文件内容成第二个文件。虽然 `System.Directory` 中已经有一个函数叫 `copyFile`，但我们想要实作自己的版本。

```haskell
import System.Environment
import qualified Data.ByteString.Lazy as B

main = do
    (fileName1:fileName2:_) <- getArgs
    copyFile fileName1 fileName2

copyFile :: FilePath -> FilePath -> IO ()
copyFile source dest = do
    contents <- B.readFile source
    B.writeFile dest contents
```

我们写了自己的函数，他接受两个 `FilePath`（记住 `FilePath` 不过是 `String` 的同义词。）并回传一个 I/O action，他会用 bytestring 拷贝第一个文件至另一个。在 `main` 函数中，我们做的只是拿到命令行引数然后调用那个函数来拿到一个 I/O action。

```haskell
$ runhaskell bytestringcopy.hs something.txt ../../something.txt
```

就算我们不用 bytestring 来写，程序最后也会长得像这样。差别在于我们会用 `B.readFile` 跟 `B.writeFile` 而不是 `readFile` 跟 `writeFile`。有很大的可能性，就是你只要 import 文件并在函数前加上 qualified 模块名，就可以把一个用正常 String 的程序改成用 ByteString。也有可能你是要反过来做，但那也不难。

当你需要更好的性能来读取许多数据，尝试用 bytestring，有很大的机会你会用很小的力气改进很多性能。我通常用正常 String 来写程序，然后在性能不好的时候把他们改成 ByteString。

## Exceptions (例外)

![](/files/-MlVFmyHn0bxDdCXyu-N)

所有的编程语言都有要处理失败的情形。这就是人生。不同的语言有不同的处理方式。在 C 里面，我们通常用非正常范围的回传值（像是 `-1` 或 null）来回传错误。Java 跟 C#则倾向于使用 exception 来处理失败的情况。当一个 exception 被丢出的时候，控制流程就会跳到我们做一些清理动作的地方，做完清理后 exception 被重新丢出，这样一些处理错误的代码可以完成他们的工作。

Haskell 有一个很棒的型态系统。Algebraic data types 允许像是 `Maybe` 或 `Either` 这种型态，我们能用这些型态来代表一些可能有或没有的结果。在 C 里面，在失败的时候回传 `-1` 是很常见的事。但他只对写程序的人有意义。如果我们不小心，我们有可能把这些错误码当作正常值来处理，便造成一些混乱。Haskell 的型态系统赋予我们更安全的环境。一个 `a -> Maybe b` 的函数指出了他会产生一个包含 `b` 的 `Just`，或是回传 `Nothing`。这型态跟 `a -> b` 是不同的，如果我们试着将两个函数混用，compiler 便会警告我们。

尽管有表达力够强的型态来辅助失败的情形，Haskell 仍然支持 exception，因为 exception 在 I/O 的 contexts 下是比较合理的。在处理 I/O 的时候会有一堆奇奇怪怪的事情发生，环境是很不能被信赖的。像是打开文件。文件有可能被 lock 起来，也有可能文件被移除了，或是整个硬盘都被拔掉。所以直接跳到处理错误的代码是很合理的。

我们了解到 I/O code 会丢出 exception 是件合理的事。至于 pure code 呢？其实他也能丢出 Exception。想想看 `div` 跟 `head` 两个案例。他们的型态是 `(Integral a) => a -> a -> a` 以及 `[a] -> a`。`Maybe` 跟 `Either` 都没有在他们的回传型态中，但他们都有可能失败。`div` 有可能除以零，而 `head` 有可能你传给他一个空的 list。

```haskell
ghci> 4 `div` 0
*** Exception: divide by zero
ghci> head []
*** Exception: Prelude.head: empty list
```

![](/files/-LjUms6tiJS8NK0EAnGO)

pure code 能丢出 Exception，但 Exception 只能在 I/O section 中被接到（也就是在 `main` 的 do block 中）这是因为在 pure code 中你不知道什么东西什么时候会被 evaluate。因为 lazy 特性的缘故，程序没有一个特定的执行顺序，但 I/O code 有。

先前我们谈过为什么在 I/O 部份的程序要越少越好。程序的逻辑部份尽量都放在 pure 的部份，因为 pure 的特性就是他们的结果只会根据函数的参数不同而改变。当思考 pure function 的时候，你只需要考虑他回传什么，因为除此之外他不会有任何副作用。这会让事情简单许多。尽管 I/O 的部份是难以避免的（像是打开文件之类），但最好是把 I/O 部份降到最低。Pure functions 缺省是 lazy，那代表我们不知道他什么时候会被 evaluate，不过我们也不该知道。然而，一旦 pure functions 需要丢出 Exception，他们何时被 evaluate 就很重要了。那是因为我们只有在 I/O 的部份才能接到 Exception。这很糟糕，因为我们说过希望 I/O 的部份越少越好。但如果我们不接 Exception，我们的程序就会当掉。这问题有解决办法吗？答案是不要在 pure code 里面使用 Exception。利用 Haskell 的型态系统，尽量使用 `Either` 或 `Maybe` 之类的型态来表示可能失败的计算。

这也是为什么我们要来看看怎么使用 I/O Excetion。I/O Exception 是当我们在 `main` 里面跟外界沟通失败而丢出的 Exception。例如我们尝试打开一个文件，结果发现他已经被删掉或是其他状况。来看看一个尝试打开命令行引数所指定文件名称，并计算里面有多少行的程序。

```haskell
import System.Environment
import System.IO

main = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
```

一个很简单的程序。我们使用 `getArgs` I/O action，并绑定第一个 string 到 `fileName`。然后我们绑定文件内容到 `contents`。最后，我们用 `lines` 来取得 line 的 list，并计算 list 的长度，并用 `show` 来转换数字成 string。他如我们想像的工作，但当我们给的文件名称不存在的时候呢？

```haskell
$ runhaskell linecount.hs i_dont_exist.txt
linecount.hs: i_dont_exist.txt: openFile: does not exist (No such file or directory)
```

GHC 丢了错误消息给我们，告诉我们文件不存在。然后程序就挂掉了。假如我们希望打印出比较好一些的错误消息呢？一种方式就是在打开文件前检查他存不存在。用 `System.Directory` 中的 **doesFileExist**。

```haskell
import System.Environment
import System.IO
import System.Directory

main = do (fileName:_) <- getArgs
            fileExists <- doesFileExist fileName
            if fileExists
                then do contents <- readFile fileName
                    putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"
                else do putStrLn "The file doesn't exist!"
```

由于 `doesFileExist` 的型态是 `doesFileExist :: FilePath -> IO Bool`，所以我们要写成 `fileExists <- doesFileExist fileName`。那代表他回传含有一个布尔值告诉我们文件存不存在的 I/O action。`doesFileExist` 是不能直接在 if expression 中使用的。

另一个解法是使用 Exception。在这个情境下使用 Exception 是没问题的。文件不存在这个 Exception 是在 I/O 中被丢出，所以在 I/O 中接起来也没什么不对。

要这样使用 Exception，我们必须使用 `System.IO.Error` 中的 **catch** 函数。他的型态是 `catch :: IO a -> (IOError -> IO a) -> IO a`。他接受两个参数，第一个是一个 I/O action。像是他可以接受一个打开文件的 I/O action。第二个是 handler。如果第一个参数的 I/O action 丢出了 Exception，则他会被传给 handler，他会决定要作些什么。所以整个 I/O action 的结果不是如预期中做完第一个参数的 I/O action，就是 handler 处理的结果。

![](/files/-LjUms6vI2N69koDGTsd)

如果你对其他语言像是 Java, Python 中 try-catch 的形式很熟，那 `catch` 其实跟他们很像。第一个参数就是其他语言中的 try block。第二个参数就是其他语言中的 catch block。其中 handler 只有在 exception 被丢出时才会被执行。

handler 接受一个 `IOError` 型态的值，他代表的是一个 I/O exception 已经发生了。他也带有一些 exception 本身的信息。至于这型态在语言中使如何被实作则是要看编译器。这代表我们没办法用 pattern matching 的方式来查看 `IOError`。就像我们不能用 pattern matching 来查看 `IO something` 的内容。但我们能用一些 predicate 来查看他们。

我们来看看一个展示 `catch` 的程序

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e = putStrLn "Whoops, had some trouble!"
```

首先你看到我们可以在关键字周围加上 backticks 来把 `catch` 当作 infix function 用，因为他刚好接受两个参数。这样使用让可读性变好。``toTry `catch` handler`` 跟 `catch toTry handler` 是一模一样的。`toTry` 是一个 I/O action，而 `handler` 接受一个 `IOError`，并回传一个当 exception 发生时被执行的 I/O action。

来看看执行的结果。

```haskell
$ runhaskell count_lines.hs i_exist.txt
The file has 3 lines!

$ runhaskell count_lines.hs i_dont_exist.txt
Whoops, had some trouble!
```

在 handler 里面我们并没有检查我们拿到的是什么样的 `IOError`，我们只是打印出 `"Whoops, had some trouble!"`。接住任何种类的 Exception 就跟其他语言一样，在 Haskell 中也不是一个好的习惯。假如其他种类的 Exception 发生了，好比说我们送一个中断指令，而我们没有接到的话会发生什么事？这就是为什么我们要做跟其他语言一样的事：就是检查我们拿到的是什么样的 Exception。如果说是我们要的 Exception，那就做对应的处理。如果不是，我们再重新丢出 Exception。我们把我们的程序这样修改，只接住文件不存在的 Exception。

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
            contents <- readFile fileName
            putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e = putStrLn "The file doesn't exist!"
    | otherwise = ioError e
```

除了 handler 以外其他东西都没变，我们只接住我们想要的 I/O exception。这边使用了 `System.IO.Error` 中的函数 **isDoesNotExistError** 跟 **ioError**。`isDoesNotExistError` 是一个运作在 `IOError` 上的 predicate ，他代表他接受一个 `IOError` 然后回传 `True` 或 `False`，他的型态是 `isDoesNotExistError :: IOError -> Bool`。我们用他来判断是否这个错误是文件不存在所造成的。我们这边使用 guard，但其实也可以用 if else。如果 exception 不是由于文件不存在所造成的，我们就用 `ioEroror` 重新丢出接到的 exception。他的型态是 `ioError :: IOException -> IO a`，所以他接受一个 `IOError` 然后产生一个会丢出 exception 的 I/O action。那个 I/O action 的型态是 `IO a`，但他其实不会产生任何结果，所以他可以被当作是 `IO anything`。

所以有可能在 `toTry` 里面丢出的 exception 并不是文件不存在造成的，而 ``toTry `catch` handler`` 会接住再丢出来，很酷吧。

程序里面有好几个运作在 `IOError` 上的 I/O action，当其中一个没有被 evaluate 成 `True` 时，就会掉到下一个 guard。这些 predicate 分别为：

* **isAlreadyExistsError**
* **isDoesNotExistError**
* **isFullError**
* **isEOFError**
* **isIllegalOperation**
* **isPermissionError**
* **isUserError**

大部分的意思都是显而易见的。当我们用了 **userError** 来丢出 exception 的时候，`isUserError` 被 evaluate 成 `True`。例如说，你可以写 `ioError $ userError "remote computer unplugged!"`，尽管用 `Either` 或 `Maybe` 来表示可能的错误会比自己丢出 exception 更好。

所以你可能写一个像这样的 handler

```haskell
handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e = putStrLn "The file doesn't exist!"
    | isFullError e = freeSomeSpace
    | isIllegalOperation e = notifyCops
    | otherwise = ioError e
```

其中 `notifyCops` 跟 `freeSomeSpace` 是一些你定义的 I/O action。如果 exception 不是你要的，记得要把他们重新丢出，不然你的程序可能只会安静地当掉。

`System.IO.Error` 也提供了一些能询问 exception 性质的函数，像是哪些 handle 造成错误，或哪些文件名造成错误。这些函数都是 `ioe` 当开头。而且你可以在文档中看到一整串详细数据。假设我们想要打印出造成错误的文件名。我们不能直接打印出从 `getArgs` 那边拿到的 `fileName`，因为只有 `IOError` 被传进 handler 中，而 handler 并不知道其他事情。一个函数只依赖于他所被调用时的参数。这也是为什么我们会用 **ioeGetFileName** 这函数，他的型态是 `ioeGetFileName :: IOError -> Maybe FilePath`。他接受一个 `IOError` 并回传一个 `FilePath`（他是 `String` 的同义词。）基本上他做的事就是从 `IOError` 中抽出文件路径。我们来修改一下我们的程序。

```haskell
import System.Environment
import System.IO
import System.IO.Error

main = toTry `catch` handler

toTry :: IO ()
toTry = do (fileName:_) <- getArgs
    contents <- readFile fileName
    putStrLn $ "The file has " ++ show (length (lines contents)) ++ " lines!"

handler :: IOError -> IO ()
handler e
    | isDoesNotExistError e =
        case ioeGetFileName e of Just path -> putStrLn $ "Whoops! File does not exist at: " ++ path
                                 Nothing -> putStrLn "Whoops! File does not exist at unknown location!"
    | otherwise = ioError e
```

在 `isDoesNotExistError` 是 `True` 的 guard 里面，我们在 case expression 中用 `e` 来调用 `ioeGetFileName`，然后用 pattern matching 拆出 `Maybe` 中的值。当你想要用 pattern matching 却又不想要写一个新的函数的时候，case expression 是你的好朋友。

你不想只用一个 `catch` 来接你 I/O part 中的所有 exception。你可以只在特定地方用 `catch` 接 exception，或你可以用不同的 handler。像这样：

```haskell
main = do toTry `catch` handler1
          thenTryThis `catch` handler2
          launchRockets
```

这边 `toTry` 使用 `handler1` 当作 handler，而 `thenTryThis` 用了 `handler2`。`launchRockets` 并不是 `catch` 的参数，所以如果有任何一个 exception 被丢出都会让我们的程序当掉，除非 `launchRockets` 使用 `catch` 来处理 exception。当然 `toTry`，`thenTryThis` 跟 `launchRockets` 都是 I/O actions，而且被 do syntax 绑在一起。这很像其他语言中的 try-catch blocks，你可以把一小段程序用 try-catch 包住，你可以自己调整该包多少进去。

现在你知道如何处理 I/O exception 了。我们并没有提到如何从 pure code 中丢出 exception，这是因为正如我们先前提到的，Haskell 提供了更好的办法来处理错误。就算是在可能会失败的 I/O action 中，我也倾向用 `IO (Either a b)`，代表他们是 I/O action，但当他们被执行，他们结果的型态是 `Either a b`，意思是不是 `Left a` 就是 `Right b`。


# ch10


# 函数式地思考来解决问题

在这一章中，我们会查看几个有趣的问题，并尝试用函数式的方式来漂亮地解决他们。我们并不会介绍新的概念，我们只是练习我们刚学到的写程序的技巧。每一节都会探讨不同的问题。会先描述问题，然后用最好的方式解决他。

## 运算逆波兰表示法(Reverse Polish notation form)

我们在学校学习数学的时候，我们多半都是用中置(infix)的方式来写数学式。例如说，我们会写 `10 - (4 + 3) * 2`。`+`, `*`, `-` 是中置运算子(infix operators)。在 Haskell 中就像是 `+` 或 `elem` 一样。这种写法对于人类来说很容易阅读与理解，但缺点是我们必须用括号来描述运算的优先级。

逆波兰表示法是另外一种数学式的描述方法。乍看之下显得怪异，但他其实很容易理解并使用。因为我们不需要括弧来描述，也很容易放进计算机里面运算。尽管现在的计算机都是用中置的方式让你输入，有些人仍坚持用 RPN 的计算机。前述的算式如果表达成 RPN 的话会是 `10 4 3 + 2 * -`。我们要如何计算他的结果呢？可以想想堆叠，基本上你是从左向右阅读算式。每当碰到一个数值，就把他堆上堆叠。当我们碰到一个运算子。就把两个数值从堆叠上拿下来，用运算子运算两个数值然后把结果推回堆叠中。当你消耗完整个算式的时候，而且假设你的算式是合法的，那你就应该只剩一个数值在堆叠中，

![](/files/-LjUmq_bViYQrsYf8sCc)

我们再接着看 `10 4 3 + 2 * -`。首先我们把 `10` 推到堆叠上，所以堆叠现在是 `10`。下一个接着的输入是 `4`，我们也把他推上堆叠。堆叠的状态便变成 `10, 4`。接着也对下一个输入 `3` 做同样的事，所以堆叠变成 `10, 4, 3`。然后便碰到了第一个运算子 `+`。我们把堆叠最上层的两个数值取下来（所以堆叠变成 `10`）把两个数值加起来然后推回堆叠上。堆叠的状态便变成 `10, 7`。我们再把输入 `2` 推上堆叠，堆叠变成 `10, 7, 2`。我们又碰到另一个运算子，所以把 `7` 跟 `2` 取下，把他们相乘起来然后推回堆叠上。`7` 跟 `2` 相乘的结果是 `14`，所以堆叠的状态是 `10, 14`。最后我们碰到了 `-`。我们把 `10` 跟 `14` 取下，将他们相减然后推回堆叠上。所以现在堆叠的状态变成 `-4`。而我们已经把所有数值跟运算子的消耗完了，所以 `-4` 便是我们的结果。

现在我们知道我们如何手算 RPN 运算式了，接下来可以思考一下我们写一个 Haskell 的函数，当他接到一个 RPN 运算式，像是 `"10 4 3 + 2 * -"` 时，他可以给出结果。

这个函数的型别会是什么样呢？我们希望他接受一个字串当作参数，并产出一个数值作为结果。所以应该会是 `solveRPN :: (Num a) => String -> a`。

```
小建议：在你去实作函数之前，先想一下你会怎么宣告这个函数的型别能够帮助你厘清问题。在 Haskell 中由于我们有够强的型别系统，光从函数的宣告就可以得到许多信息。
```

![](/files/-LjUmq_jCq9qvgdk5KHK)

当我们要实作一个问题的解法时，你可以先动手一步一步解看看，尝试从里面得到一些灵感。我们这边把每一个用空白隔开的数值或运算子都当作独立的一项。所以把 `"10 4 3 + 2 * -"` 这样一个字串断成一串 list `["10","4","3","+","2","*","-"]` 应该会有帮助。

接下来我们要如何应用这个断好的 list 呢？我们从左至右来走一遍，并保存一个工作用的堆叠。这样有让你想到些什么可以用的吗？没错，在 `folds` 的那一章里面，我们提到基本上当你需要从左至右或由右至左走过一遍 list 的时候并产生些结果的时候。我们都能用 fold 来实作他。

在这个 case 中由于我们是从左边走到右边，所以我们采取 left fold。accumulator 则是选用堆叠，而 fold 的结果也会是一个堆叠，只是里面只有一个元素而已。

另外要多考虑一件事是我们用什么来代表我们的堆叠？我们可以用 list 来代替，list 的 head 就可以当作是堆叠的顶端。毕竟要把一个元素加到 list 的 head 要比加到最后要有效率多。所以如果我们有一个堆叠，里面有 `10, 4, 3`，那我们可以用 `[3,4,10]` 来代表他。

现在我们有了足够的信息来写出我们的函数。他会接受一个字串 `"10 4 3 + 2 * -"`，随即用 `words` 来断成 list `["10","4","3","+","2","*","-"]`。接下来我们做一个 left fold 来产生出只有一个元素的堆叠，也就是 `[-4]`。我们把这个元素从 list 取出便是最后的结果。

来看看我们的实作：

```haskell
import Data.List  

solveRPN :: (Num a) => String -> a  
solveRPN expression = head (foldl foldingFunction [] (words expression))  
    where   foldingFunction stack item = ...
```

我们接受一个运算式并把他断成一串 List。然后我们用一个 folding 函数来 fold 这串 list。注意到我们用 `[]` 来当作起始的 accumulator。这个 accumulator 就是我们的堆叠，所以 `[]` 代表一个空的堆叠。在运算之后我们得到一个只有一个元素的堆叠，我们调用 `head` 来取出他并用 `read` 来转换他。

所以我们现在只缺一个接受堆叠的 folding 函数，像是可以接受 `[4,10]` 跟 `"3"`，然后得到 `[3,4,10]`。如果是 `[4,10]` 跟 `"*"`，那就会得到 `[40]`。但在实作之前，我们先把我们的函数改写成 point-free style，这样可以省下许多括号。

```haskell
import Data.List  

solveRPN :: (Num a) => String -> a  
solveRPN = head . foldl foldingFunction [] . words  
      where   foldingFunction stack item = ...
```

看起来好多了。我们的 folding 函数会接受一个堆叠、新的项，并回传一个新的堆叠。我们使用模式匹配的方式来取出堆叠最上层的元素，然后对 `"*"` 跟 `"-"` 做匹配。

```haskell
solveRPN :: (Num a, Read a) => String -> a  
solveRPN = head . foldl foldingFunction [] . words  
    where   foldingFunction (x:y:ys) "*" = (x * y):ys  
            foldingFunction (x:y:ys) "+" = (x + y):ys  
            foldingFunction (x:y:ys) "-" = (y - x):ys  
            foldingFunction xs numberString = read numberString:xs
```

我们用展开成四个模式匹配。模式会从第一个开始尝试匹配。所以 folding 函数会看看目前的项是否是 `"*"`。如果是，那就会将 `[3,4,9,3]` 的头两个元素绑定到 `x`，`y` 两个名称。所以 `x` 会是 `3` 而 `y` 等于 `4`。`ys` 便会是 `[9,3]`。他会回传一个 list，只差在 `x` 跟 `y` 相乘的结果为第一个元素。也就是说会把最上层两个元素取出，相乘后再放回去。如果第一个元素不是 `"*"`，那模式匹配就会比对到 `"+"`，以此类推。

如果项并未匹配到任何一个运算子，那我们就会假设这个字串是一个数值。如果他是一个数值，我们会用 `read` 来把字串转换成数值。并把这个数值推到堆叠上。

另外注意到我们加了 `Read a` 这像 class constraint，毕竟我们要使用到 `read` 来转换成数值。所以我们必须要宣告成他要属于 `Num` 跟 `Read` 两种 typeclass。（譬如说 `Int`,`Float` 等）

我们是从左至右走过 `["2","3","+"]`。一开始堆叠的状态是 `[]`。首先他会用 `[]` 跟 `"2"` 来喂给 folding 函数。由于此项并不是一个运算子。他会用 `read` 读取后加到 `[]` 的开头。所以堆叠的状态变成 `[2]`。接下来就是用 `[2]` 跟 `["3"]` 来喂给 folding 函数，而得到 `[3,2]`。最后再用 `[3,2]` 跟 `"+"` 来调用 folding 函数。这会堆叠顶端的两个数值，加起来后推回堆叠。最后堆叠变成 `[5]`，这就是我们回传的数值。

我们来试试看我们新写的函数：

```haskell
ghci> solveRPN "10 4 3 + 2 * -"  
-4  
ghci> solveRPN "2 3 +"  
5  
ghci> solveRPN "90 34 12 33 55 66 + * - +"  
-3947  
ghci> solveRPN "90 34 12 33 55 66 + * - + -"  
4037  
ghci> solveRPN "90 34 12 33 55 66 + * - + -"  
4037  
ghci> solveRPN "90 3 -"  
87
```

看起来运作良好。这个函数有一个特色就是他很容易改写来支持额外的运算子。他们也不一定要是二元运算子。例如说我们可以写一个运算子叫做 `"log"`，他会从堆叠取出一个数值算出他的 log 后推回堆叠。我们也可以用三元运算子来从堆叠取出三个数值，并把结果放回堆叠。甚至是像是 `"sum"` 这样的运算子，取出所有数值并把他们的和推回堆叠。

我们来改写一下我们的函数让他多支持几个运算子。为了简单起见，我们改写宣告让他回传 `Float` 型别。

```haskell
import Data.List  

solveRPN :: String -> Float  
solveRPN = head . foldl foldingFunction [] . words  
where   foldingFunction (x:y:ys) "*" = (x * y):ys  
        foldingFunction (x:y:ys) "+" = (x + y):ys  
        foldingFunction (x:y:ys) "-" = (y - x):ys  
        foldingFunction (x:y:ys) "/" = (y / x):ys  
        foldingFunction (x:y:ys) "^" = (y ** x):ys  
        foldingFunction (x:xs) "ln" = log x:xs  
        foldingFunction xs "sum" = [sum xs]  
        foldingFunction xs numberString = read numberString:xs
```

看起来不错，没有疑问地 `/` 是除法而 `**` 是取 exponential。至于 log 运算子，我们只需要模式匹配一个元素，毕竟 log 只需要一个元素。而 sum 运算子，我们只回传一个仅有一个元素的堆叠，包含了所有元素的和。

```haskell
ghci> solveRPN "2.7 ln"  
0.9932518  
ghci> solveRPN "10 10 10 10 sum 4 /"  
10.0  
ghci> solveRPN "10 10 10 10 10 sum 4 /"  
12.5  
ghci> solveRPN "10 2 ^"  
100.0
```

由于 `read` 知道如何转换浮点数，我们也可在运算适中使用他。

```haskell
ghci> solveRPN "43.2425 0.5 ^"  
6.575903
```

有这样一个容易拓展到浮点数而且动到的代码又在十行以内的函数，我想是非常棒的。

有一件事要留意的是这个函数对于错误处理并不好。当我们碰到非法输入的时候，他就会直接当掉。之后我们碰到 Monad 的时候我们会写一个容错的版本，他的型别会是 `solveRPN :: String -> Maybe Float`。当然我们现在也可以写一个，不过那会有点麻烦，因为会有一大堆检查 `Nothing` 的动作。如果你希望挑战的话，也可以尽管尝试。（提示：你可以用 `reads` 来看看一次 read 是否会成功）

## 路径规划

我们接下来的问题是：你的飞机刚刚降落在英格兰的希思罗机场。你接下来有一个会议，你租了一台车希望尽速从机场前往伦敦市中心。

从希思罗机场到伦敦有两条主要道路，他们中间有很多小路连接彼此。如果你要走小路的话都会花掉一定的时间。你的问题就是要选一条最佳路径让你可以尽快前往伦敦。你从图的最左边出发，中间可能穿越小路来前往右边。

![](/files/-LjUmq_ng9QigF7uGS8S)

你可以从图中看到，从希思罗机场到伦敦在这个路径配置下的最短路径是先选主要道路 B，经由小路到 A 之后，再走一小段，转到 B 之后继续往前走。如果采取这个路径的话，会花去 75 分钟。如果选其他道路的话，就会花更多时间。

我们任务就是要写一个程序，他接受道路配置的输入，然后印出对应的最短路径。我们的输入看起来像是这样：

```haskell
50  
10  
30  
5  
90  
20  
40  
2  
25  
10  
8  
0
```

我们在心中可以把输入的数值三个三个看作一组。每一组由道路 A,道路 B,还有交叉的小路组成。而要能够这样组成，我们必须让最后有一条虚拟的交叉小路，只需要走 0 分钟就可以穿越他。因为我们并不会在意在伦敦里面开车的成本，毕竟我们已经到达伦敦了。

正如我们在解 RPN 计算机的问题的时候，我们是用三步骤来解题：

* 首先忘掉 Haskell，想想我们自己是怎么一步步解题的。
* 想想如何在 Haskell 中表达我们的数据。
* 在 Haskell 中要如何对这些数据做运算来产生出解答。

在介绍 RPN 计算机的章节中，我们首先自己用人脑计算表达式，在心中维持一个堆叠然后一项一项处理。我们决定用一个字串来表达我们的表达式。最后，我们用 left fold 来走过我们这一串 list，并算出结果。

究竟我们要怎么用手算出从希思罗机场到伦敦的最短路径呢？我们可以观察整章图片，猜测哪一条是最短路径然后希望我们有猜对。这样的作法对于很小的输入可以成功，但如果我们的路径超过 10000 组呢？这样我们不知道我们的解法是不是最佳解，我们只能说可能是。

所以那并不是一个好作法。这边有一张简化过后的图。

![](/files/-LjUmq_p9Iby0YGY3OuF)

你能想出来到道路 A 上第一个交叉点的最短路径吗？（标记成 A1 的点）这太容易了。我们只要看看从道路 A 出发或是从道路 B 出发穿越至道路 A 两种作法哪种比较短就好。很明显的，从道路 B 出发的比较短，只要花费 40 分钟，然而从道路 A 则要花费 50 分钟。那到交叉点 B1 呢？同样的作法可以看出从道路 B 出发只要花费 10 分钟，远比从道路 A 出发然后穿越小路要花费少，后者要花费 80 分钟！

现在我们知道要到达 A1 的最短路径是经由 B 然后邹小路到达，共花费 40。而我们知道要达到 B1 的最短路径则是直接走 B，花费 10。这样的知识有办法帮助我们得知到下一个交叉点的最短路径吗？可以的。

我们来看看到达 A2 的最短路径是什么。要到达 A2，我们必须要从 A1 走到 A2 或是从 B1 走小路。由于我们知道到达 A1 跟 B1 的成本，我们可以很容易的想出到达 A2 的最佳路径。到达 A1 要花费 40，而从 A1 到 A2 需要 5。所以 `B, C, A` 总共要 45。而要到达 B1 只要 10，但需要额外花费 110 分钟来到达 B2 然后走小路到达 A2。所以最佳路径就是 `B, C, A`。同样地到达 B2 最好的方式就是走 A1 然后走小路。

```
也许你会问如果先在 B1 跨到道路 A 然后走到 A2 的情况呢？我们已经考虑过了从 B1 到 A1 的情况，所以我们不需要再把他考虑进去。
```

现在我们有了至 A2 跟 B2 的最佳路径，我们可以一直重复这个过程直到最右边。一旦我们到达了 A4 跟 B4，那其中比较短的就是我们的最佳路径了。

基本上对于第二组而言，我们只是不断地重复之前的步骤，只是我们考虑进在前面的最佳路径而已。当然我们也可以说在第一步就考虑进了前面的最佳路径，只是他们都是 0 而已。

总结一下。要得到从希思罗机场到伦敦的最短路径。我们首先看看到达下一个道路 A 上的交叉点的最短路径。共有两种选择的路径，一是直接从道路 A 出发然后走到交叉点，要不然就是从道路 B 出发，走到第一个交叉点然后走小路。得到结果后记住结果。接着再用同样的方法来得到走到道路 B 上下一个交叉点的最短路径，并也记住结果。然后我们看看要走到再下一个道路 A 上的交叉点，究竟是从这个道路 A 上的交叉点往前走，或是从对应的道路 B 上的交叉点往前走再走到对面，两种选择哪种比较好。记下比较好的选择，然后也对对应的道路 B 上的交叉点做一次这个过程。做完全部组之后就到达最右边。一旦到达最右边，最佳的选择就是我们的最短路径了。

基本上当我们到达最右边的时候，我们记下了最后停在道路 A 的最短路径跟最后停在道路 B 的最短路径。其中比较短的是我们真正的最短路径。现在我们已经知道怎么用手算出答案。如果你有闲工夫，你可以拿纸笔对于任何一组道路配置算出他的最短路径。

接下来的问题是，我们要如何用 Haskell 的型别来代表这里的道路配置呢？一种方式就是把起始点跟交叉点都当作图的节点，并连到其他的交叉点。如果我们想像其实起点也有一条长度为 1 的虚拟道路连接彼此，那每个交叉点或是节点就都连接对面的节点了。同时他们也连到下一个交叉点。唯一的例外是最后一个节点，他们只连接到对面。

```haskell
data Node = Node Road Road | EndNode Road  
data Road = Road Int Node
```

一个节点要码是一个普通的节点，他包含有通往下一个交叉点的路径信息，还有往对面道路的信息。或是一个终端点，只包含往对面节点的道路信息。一条道路包含他多长，还有他指向哪里。例如说，道路 A 的第一个部份就可写成 `Road 50 a1`。其中 `a1` 是 `Node x y` 这样一个节点。而 `x` 跟 `y` 则分别指向 B1 跟 A2。

另一种方式就是用 `Maybe` 来代表往下一个交叉点走的路。每个节点有指到对面节点的路径部份，但只有不是终端节点的节点才有指向下一个交叉点的路。

```haskell
data Node = Node Road (Maybe Road)  
data Road = Road Int Node
```

这些是用 Haskell 来代表道路系统的方式，而我们也能靠他们来解决问题。但也许我们可以想出更简单的模型？如果我们想想之前手算的方式，我们每次检查都只有检查三条路径的长度而已。在道路 A 的部份，跟在道路 B 的部份，还有接触两个部份并将他们连接起来的部份。当我们观察到 A1 跟 B1 的最短路径时，我们只考虑第一组的三个部份，他们分别花费 50, 10 跟 30。所以道路系统可以用四组来表示：`50, 10, 30`，`5, 90, 20`，`40, 2, 25` 跟 `10, 8, 0`。

让我们数据型别越简单越好，不过这样已经是极限了。

```haskell
data Section = Section { getA :: Int, getB :: Int, getC :: Int } deriving (Show)  
type RoadSystem = [Section]
```

这样很完美，而且对于我们的实作也有帮助。`Section` 是一个 algebraic data type，包含三个整数，分别代表三个不同部份的道路长。我们也定义了型别同义字，说 `RoadSystem` 代表包含 section 的 list。

```
当然我们也可以用一个 tuple ``(Int, Int, Int)`` 来代表一个 section。使用 tuple 对于一些简单的情况是比较方便，但对于比较复杂的情况定义自己的 algebraic data type 会比较好。他让型别系统获得比较多的信息。``(Int, Int, Int)`` 毕竟也可能被使用在定义三维空间中的一个矢量，只用 tuple 让我们可能把这两种情形混杂起来使用。如果我们用 ``Section`` 跟 ``Vector`` 的话就不会不小心搞混了。
```

从希思罗机场到伦敦的道路系统便可以这样表示：

```haskell
heathrowToLondon :: RoadSystem  
heathrowToLondon = [Section 50 10 30, Section 5 90 20, Section 40 2 25, Section 10 8 0]
```

我们现在要做的就是用 Haskell 实作我们先前的解法。所以我们应该怎样宣告我们计算最短路径函数的型别呢？他应该接受一个道路系统作为参数，然后回传一个路径。我们会用一个 list 来代表我们的路径。我们定义了 `Label` 来表示 `A`, `B` 或 `C`。并且也定义一个同义词 `Path`：

```haskell
data Label = A | B | C deriving (Show)  
type Path = [(Label, Int)]
```

而我们的函数 `optimalPath` 应该要有 `optimalPath :: RoadSystem -> Path` 这样的型别。如果被喂给 `heathrowToLondon` 这样的道路系统，他应该要回传下列的路径：

```haskell
[(B,10),(C,30),(A,5),(C,20),(B,2),(B,8)]
```

我们接下来就从左至右来走一遍 list，并沿路上记下 A 的最佳路径跟 B 的最佳路径。我们会 accumulate 我们的最佳路径。这听起来有没有很熟悉？没错！就是 left fold。

当我们手动做解答的时候，有一个步骤是我们不断重复的。就是检查现有 A 跟 B 的最佳路径以及目前的 section，产生出新的 A 跟 B 的最佳路径。举例来说，最开始我们的最佳路径是 `[]` 跟 `[]`。我们看过 `Section 50 10 30` 后就得到新的到 A1 的最佳路径为 `[(B,10),(C,30)]`，而到 B1 的最佳路径是 `[(B,10)]`。如果你们把这个步骤看作是一个函数，他接受一对路径跟一个 section，并产生出新的一对路径。所以型别是 `(Path, Path) -> Section -> (Path, Path)`。我们接下来继续实作这个函数。

```
提示：把 ``(Path, Path) -> Section -> (Path, Path)`` 当作 left fold 用的二元函数，fold 要求的型态是 ``a -> b -> a``。
```

```haskell
roadStep :: (Path, Path) -> Section -> (Path, Path)  
roadStep (pathA, pathB) (Section a b c) =   
    let priceA = sum $ map snd pathA  
        priceB = sum $ map snd pathB  
        forwardPriceToA = priceA + a  
        crossPriceToA = priceB + b + c  
        forwardPriceToB = priceB + b  
        crossPriceToB = priceA + a + c  
        newPathToA = if forwardPriceToA <= crossPriceToA  
                        then (A,a):pathA  
                        else (C,c):(B,b):pathB  
        newPathToB = if forwardPriceToB <= crossPriceToB  
                        then (B,b):pathB  
                        else (C,c):(A,a):pathA  
    in  (newPathToA, newPathToB)
```

![](/files/-LjUmq_rZA_pAOSDKOhF)

上面的程序究竟写了些什么？首先他根据先前 A 的最佳解计算出道路 A 的最佳解，之后也如法炮制计算 B 的最佳解。使用 `sum $ map snd pathA`，所以如果 `pathA` 是 `[(A,100),(C,20)]`。`priceA` 就是 `120`。`forwardPriceToA` 就会是我们要付的成本。如果我们是从先前在 A 上的交叉点前往。那他就会等于我们至先前交叉点的最佳解加上目前 section 中 A 的部份。`crossPriceToA` 则是我们从先前在 B 上的交叉点前往 A 所要付出的代价。他是先前 B 的最佳解加上 section 中 B 的部份加上 C 的长。同样地方式也可以决定 `forwardPriceToB` 跟 `crossPriceToB`。

现在我们知道了到 A 跟 B 的最佳路径，我们需要根据这些信息来构造到 A 跟 B 的整体路径。如果直接走到 A 耗费较少的话，我们就把 `newPathToA` 设置成 `(A,a):pathA`。这样做的事就是把 `Label` `A` 跟 section 的长度 `a` 接到最佳路径的前面。要记得 `A` 是一个 label，而 `a` 的型别是 `Int`。我们为什么要接在前面而不是 `pathA ++ [(A,a)]` 呢？因为接在 list 的前面比起接在后端要有效率多了。不过这样产生出来的 list 就会相反。但要把 list 再反过来并不难。如果先走到 B 再穿越小路走到 A 比较短的话，那 `newPathToA` 就会包含这样走的路线。同样的道理也可以套用在 `newPathToB` 上。

最后我们回传 `newPathToA` 跟 `newPathToB` 这一对结果。

我们把 `heathrowToLondon` 的第一个 section 喂给我们的函数。由于他是第一个 section，所以到 A 跟 B 的最佳路径就是一对空的 list。

```haskell
ghci> roadStep ([], []) (head heathrowToLondon)  
([(C,30),(B,10)],[(B,10)])
```

要记住包含的路径是反过来的，要从右边往左边读。所以到 A 的最佳路径可以解读成从 B 出发，然后穿越到道路 A。而 B 的最佳路径则是直接从 B 出发走到下一个交叉点。

```
优化小技巧：当我们写 ``priceA = sum $ map snd pathA`` 的时候。我们是在计算每步的成本。如果我们实作 ``roadStep`` 成 ``(Path, Path, Int, Int) -> Section -> (Path, Path, Int, Int)`` 的话就可以不必那么做。其中的整数型别代表 A 跟 B 上的最小成本。
```

现在我们有了一个函数他接受一对路径跟一个 section，并产生新的最佳路径。我们可以用一个 left fold 来做。我们用 `([],[])` 跟第一个 section 来喂给 `roadStep` 并得到一对最佳路径。然后他又被喂给这个新得到的最佳路径跟下一个 section。以此类推。当我们走过全部的 section 的时候，我们就会得到一对最佳路径，而其中比较短的那个就是解答。有了这样的想法，我们便可以实作 `optimalPath`。

```haskell
optimalPath :: RoadSystem -> Path  
optimalPath roadSystem = 
    let (bestAPath, bestBPath) = foldl roadStep ([],[]) roadSystem  
    in  if sum (map snd bestAPath) <= sum (map snd bestBPath)  
                then reverse bestAPath  
                else reverse bestBPath
```

我们对 `roadSystem` 做 left fold。而用的起始 accumulator 是一对空的路径。fold 的结果也是一对路径，我们用模式匹配的方式来把路径从结果取出。然后我们检查哪一个路径比较短便回传他。而且在回传之前也顺便把整个结果反过来。因为我们先前提到的我们是用接在前头的方式来构造结果的。

我们来测试一下吧！

```haskell
ghci> optimalPath heathrowToLondon  
[(B,10),(C,30),(A,5),(C,20),(B,2),(B,8),(C,0)]
```

这正是我们应该得到的结果！不过跟我们预期的结果仍有点差异，在最后有一步 `(C,0)`，那代表我们已经在伦敦了仍然跨越小路。不过由于他的成本是 0，所以依然可以算做正确的结果。

我们找出最佳路径的函数，现在要做的只需要从标准输入读取文本形式道路系统，并把他转成 `RoadSystem`，然后用 `optimalPath` 来把他跑一遍就好了。

首先，我们写一个函数，他接受一串 list 并把他切成同样大小的 group。我们命名他为 `groupOf`。当参数是 `[1..10]` 时，`groupOf 3` 应该回传 `[[1,2,3],[4,5,6],[7,8,9],[10]]`。

```haskell
groupsOf :: Int -> [a] -> [[a]]  
groupsOf 0 _ = undefined  
groupsOf _ [] = []  
groupsOf n xs = take n xs : groupsOf n (drop n xs)
```

一个标准的递归函数。对于 `xs` 等于 `[1..10]` 且 `n` 等于 `3`，这可以写成 `[1,2,3] : groupsOf 3 [4,5,6,7,8,9,10]`。当这个递归结束的时候，我们的 list 就三个三个分好组。而下列是我们的 `main` 函数，他从标准输入读取数据，构造 `RoadSystem` 并印出最短路径。

```haskell
import Data.List  

main = do  
    contents <- getContents  
    let threes = groupsOf 3 (map read $ lines contents)  
        roadSystem = map (\[a,b,c] -> Section a b c) threes  
        path = optimalPath roadSystem  
        pathString = concat $ map (show . fst) path  
        pathPrice = sum $ map snd path  
    putStrLn $ "The best path to take is: " ++ pathString  
    putStrLn $ "The price is: " ++ show pathPrice
```

首先，我们从标准输入获取所有的数据。然后我们调用 `lines` 来把 `"50\n10\n30\n...` 转换成 `["50","10","30"..`，然后我们 map `read` 来把这些转成包含数值的 list。我们调用 `groupsOf 3` 来把 list 的 list，其中子 list 长度为 3。我们接着对这个 list 来 map 一个 lambda `(\[a,b,c] -> Section a b c)`。正如你看到的，这个 lambda 接受一个长度为 3 的 list 然后把他变成 Section。所以 `roadSystem` 现在就是我们的道路配置，而且是正确的型别 `RoadSystem`。我们调用 `optimalPath` 而得到一个路径跟对应的代价，之后再印出来。

我们将下列文本存成文件。

```haskell
50  
10  
30  
5  
90  
20  
40  
2  
25  
10  
8  
0
```

存成一个叫 `paths.txt` 的文件然后喂给我们的程序。

```haskell
$ cat paths.txt | runhaskell heathrow.hs  
The best path to take is: BCACBBC  
The price is: 75
```

执行成功！你可以用你对 `Data.Random` 的了解来产生一个比较大的路径配置，然后你可以把产生的乱数数据喂给你的程序。如果你碰到堆叠溢出，试试看用 `foldl'` 而不要用 `foldl`。`foldl'` 是 strict 的可以减少内存消耗。


# ch11


# Functors, Applicative Functors 与 Monoids

Haskell 的一些特色，像是纯粹性，高端函数，algebraic data types，typeclasses，这些让我们可以从更高的角度来看到 polymorphism 这件事。不像 OOP 当中需要从庞大的型态阶层来思考。我们只需要看看手边的型态的行为，将他们跟适当地 typeclass 对应起来就可以了。像 `Int` 的行为跟很多东西很像。好比说他可以比较相不相等，可以从大到小排列，也可以将他们一一穷举出来。

Typeclass 的运用是很随意的。我们可以定义自己的数据型态，然后描述他可以怎样被操作，跟 typeclass 关联起来便定义了他的行为。由于 Haskell 强大的型态系统，这让我们只要读函数的型态宣告就可以知道很多信息。typeclass 可以定义得很抽象很 general。我们之前有看过 typeclass 定义了可以比较两个东西是否相等，或是定义了可以比较两个东西的大小。这些是既抽象但又描述简洁的行为，但我们不会认为他们有什么特别之处，因为我们时常碰到他们。最近我们看过了 functor，基本上他们是一群可以被 map over 的对象。这是其中一个例子能够抽象但又漂亮地描述行为。在这一章中，我们会详加阐述 functors，并会提到比较强一些的版本，也就是 applicative functors。我们也会提到 monoids。

## 温习 Functors

![](/files/-MlVFmop6Eapichamq_G)

我们已经在之前的章节提到 functors。如果你还没读那个章节，也许你应该先去看看。或是你直接假装你已经读过了。

来快速复习一下：Functors 是可以被 map over 的对象，像是 lists，`Maybe`，trees 等等。在 Haskell 中我们是用 `Functor` 这个 typeclass 来描述他。这个 typeclass 只有一个 method，叫做 `fmap`，他的型态是 `fmap :: (a -> b) -> f a -> f b`。这型态说明了如果给我一个从 `a` 映到 `b` 的函数，以及一个装了 `a` 的盒子，我会回给你一个装了 `b` 的盒子。就好像用这个函数将每个元素都转成 `b` 一样

```
*给一点建议*。这盒子的比喻尝试让你抓到些 functors 是如何运作的感觉。在之后我们也会用相同的比喻来比喻 applicative functors 跟 monads。在多数情况下这种比喻是恰当的，但不要过度引申，有些 functors 是不适用这个比喻的。一个比较正确的形容是 functors 是一个计算语境（computational context）。这个语境可能是这个 computation 可能带有值，或是有可能会失败（像 ``Maybe`` 跟 ``Either a``），或是他可能有多个值（像 lists），等等。
```

如果一个 type constructor 要是 `Functor` 的 instance，那他的 kind 必须是 `* -> *`，这代表他必须刚好接受一个 type 当作 type parameter。像是 `Maybe` 可以是 Functor 的一个 instance，因为他接受一个 type parameter，来做成像是 `Maybe Int`，或是 `Maybe String`。如果一个 type constructor 接受两个参数，像是 `Either`，我们必须给他两个 type parameter。所以我们不能这样写：`instance Functor Either where`，但我们可以写 `instance Functor (Either a) where`，如果我们把 `fmap` 限缩成只是 `Either a` 的，那他的型态就是 `fmap :: (b -> c) -> Either a b -> Either a c`。就像你看到的，`Either a` 的是固定的一部分，因为 `Either a` 只恰好接受一个 type parameter，但 `Either` 则要接受两个 type parameters。这样 fmap 的型态变成 `fmap :: (b -> c) -> Either b -> Either c`，这不太合理。

我们知道有许多态态都是 `Functor` 的 instance，像是 `[]`，`Maybe`，`Either a` 以及我们自己写的 `Tree`。我们也看到了如何用一个函数 map 他们。在这一章节，我们再多举两个例子，也就是 `IO` 跟 `(->) r`。

如果一个值的型态是 `IO String`，他代表的是一个会被计算成 String 结果的 I/O action。我们可以用 do syntax 来把结果绑定到某个名称。我们之前把 I/O action 比喻做长了脚的盒子，会到真实世界帮我们取一些值回来。我们可以查看他们取了什么值，但一旦看过，我们必须要把值放回盒子中。用这个比喻，`IO` 的行为就像是一个 functor。

我们来看看 `IO` 是怎么样的一个 `Functor` instance。当我们 `fmap` 用一个 function 来 map over I/O action 时，我们会想要拿回一个装着已经用 function 映射过值的 I/O action。

```haskell
instance Functor IO where
    fmap f action = do
        result <- action
        return (f result)
```

对一个 I/O action 做 map over 动作的结果仍会是一个 I/O action，所以我们才用 do syntax 来把两个 I/O action 黏成一个。在 `fmap` 的实作中，我们先执行了原本传进的 I/O action，并把结果绑定成 `result`。然后我们写了 `return (f result)`。`return` 就如你所知道的，是一个只会回传包了你传给他东西的 I/O action。还有一个 do block 的回传值一定是他最后一个 I/O action 的回传值。这也是为什么我们需要 return。其实他只是回传包了 `f result` 的 I/O action。

我们可以再多实验一下来找到些感觉。来看看这段 code：

```haskell
main = do line <- getLine   
        let line' = reverse line  
        putStrLn $ "You said " ++ line' ++ " backwards!"  
        putStrLn $ "Yes, you really said" ++ line' ++ " backwards!"
```

这程序要求用户输入一行文本，然后印出一行反过来的。 我们可以用 `fmap` 来改写：

```haskell
main = do line <- fmap reverse getLine  
            putStrLn $ "You said " ++ line ++ " backwards!"  
            putStrLn $ "Yes, you really said" ++ line ++ " backwards!"
```

![](/files/-MlVFmosSpfvJNkbIVOK)

就像我们用 `fmap` `reverse` 来 map over `Just "blah"` 会得到 `Just "halb"`，我们也可以 `fmap` `reverse` 来 map over `getLine`。`getLine` 是一个 I/O action，他的 type 是 `IO String`，而用 `reverse` 来 map over 他会回传一个取回一个字串并 `reverse` 他的 I/O action。就像我们 apply 一个 function 到一个 `Maybe` 一样，我们也可以 apply 一个 function 到一个 `IO`，只是这个 `IO` 会跑去外面拿回某些值。然后我们把结果用 `<-` 绑定到某个名称，而这个名称绑定的值是已经 `reverse` 过了。

而 `fmap (++"!") getLine` 这个 I/O action 表现得就像 `getLine`，只是他的结果多了一个 `"!"` 在最后。

如果我们限缩 `fmap` 到 `IO` 型态上，那 fmap 的型态是 `fmap :: (a -> b) -> IO a -> IO b`。`fmap` 接受一个函数跟一个 I/O action，并回传一个 I/O action 包含了已经 apply 过 function 的结果。

如果你曾经注意到你想要将一个 I/O action 绑定到一个名称上，只是为了要 apply 一个 function。你可以考虑使用 `fmap`，那会更漂亮地表达这件事。或者你想要对 functor 中的数据做 transformation，你可以先将你要用的 function 写在 top level，或是把他作成一个 lambda function，甚至用 function composition。

```haskell
import Data.Char  
import Data.List  

main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine  
          putStrLn line
```

```haskell
$ runhaskell fmapping_io.hs  
hello there  
E-R-E-H-T- -O-L-L-E-H
```

正如你想的，`intersperse '-' . reverse . map toUpper` 合成了一个 function，他接受一个字串，将他转成大写，然后反过来，再用 `intersperse '-'` 安插'-'。他是比较漂亮版本的 `(\xs -> intersperse '-' (reverse (map toUpper xs)))`。

另一个 `Functor` 的案例是 `(->) r`，只是我们先前没有注意到。你可能会困惑到底 `(->) r` 究竟代表什么？一个 `r -> a` 的型态可以写成 `(->) r a`，就像是 `2 + 3` 可以写成 `(+) 2 3` 一样。我们可以从一个不同的角度来看待 `(->) r a`，他其实只是一个接受两个参数的 type constructor，好比 `Either`。但记住我们说过 `Functor` 只能接受一个 type constructor。这也是为什么 `(->)` 不是 `Functor` 的一个 instance，但 `(->) r` 则是。如果程序的语法允许的话，你也可以将 `(->) r` 写成 `(r ->)`。就如 `(2+)` 代表的其实是 `(+) 2`。至于细节是如何呢？我们可以看看 `Control.Monad.Instances`。

```
我们通常说一个接受任何东西以及回传随便一个东西的函数型态是 ``a -> b``。``r -> a`` 是同样意思，只是把符号代换了一下。
```

```haskell
instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))
```

如果语法允许的话，他可以被写成

```haskell
instance Functor (r ->) where  
    fmap f g = (\x -> f (g x))
```

但其实是不允许的，所以我们必须写成第一种的样子。

首先我们来看看 `fmap` 的型态。他的型态是 `fmap :: (a -> b) -> f a -> f b`。我们把所有的 `f` 在心里代换成 `(->) r`。则 `fmap` 的型态就变成 `fmap :: (a -> b) -> ((->) r a) -> ((->) r b)`。接着我们把 `(->) r a` 跟 `(->) r b` 换成 `r -> a` 跟 `r -> b`。则我们得到 `fmap :: (a -> b) -> (r -> a) -> (r -> b)`。

从上面的结果看到将一个 function map over 一个 function 会得到另一个 function，就如 map over 一个 function 到 `Maybe` 会得到一个 `Maybe`，而 map over 一个 function 到一个 list 会得到一个 list。而 `fmap :: (a -> b) -> (r -> a) -> (r -> b)` 告诉我们什么？他接受一个从 `a` 到 `b` 的 function，跟一个从 `r` 到 `a` 的 function，并回传一个从 `r` 到 `b` 的 function。这根本就是 function composition。把 `r -> a` 的输出接到 `a -> b` 的输入，的确是 function composition 在做的事。如果你再仔细看看 instance 的定义，会发现真的就是一个 function composition。

```haskell
instance Functor ((->) r) where  
    fmap = (.)
```

这很明显就是把 `fmap` 当 composition 在用。可以用 `:m + Control.Monad.Instances` 把模块装载进来，并做一些尝试。

```haskell
ghci> :t fmap (*3) (+100)  
fmap (*3) (+100) :: (Num a) => a -> a  
ghci> fmap (*3) (+100) 1  
303  
ghci> (*3) `fmap` (+100) $ 1  
303  
ghci> (*3) . (+100) $ 1  
303  
ghci> fmap (show . (*3)) (*100) 1  
"300"
```

我们调用 `fmap` 的方式是 infix 的方式，这跟 `.` 很像。在第二行，我们把 `(*3)` map over 到 `(+100)` 上，这会回传一个先把输入值 `(+100)` 再 `(*3)` 的 function，我们再用 `1` 去调用他。

到这边为止盒子的比喻还适用吗？如果你硬是要解释的话还是解释得通。当我们将 `fmap (+3)` map over `Just 3` 的时候，对于 `Maybe` 我们很容易把他想成是装了值的盒子，我们只是对盒子里面的值 `(+3)`。但对于 `fmap (*3) (+100)` 呢？你可以把 `(+100)` 想成是一个装了值的盒子。有点像把 I/O action 想成长了脚的盒子一样。对 `(+100)` 使用 `fmap (*3)` 会产生另一个表现得像 `(+100)` 的 function。只是在算出值之前，会再多计算 `(*3)`。这样我们可以看出来 `fmap` 表现得就像 `.` 一样。

`fmap` 等同于 function composition 这件事对我们来说并不是很实用，但至少是一个有趣的观点。这也让我们打开视野，看到盒子的比喻不是那么恰当，functors 其实比较像 computation。function 被 map over 到一个 computation 会产生经由那个 function 映射过后的 computation。

![](/files/-MlVFmovqOPwYNEYyk6H)

在我们继续看 `fmap` 该遵守的规则之前，我们再看一次 `fmap` 的型态，他是 `fmap :: (a -> b) -> f a -> f b`。很明显我们是在讨论 Functor，所以为了简洁，我们就不写 `(Functor f) =>` 的部份。当我们在学 curry 的时候，我们说过 Haskell 的 function 实际上只接受一个参数。一个型态是 `a -> b -> c` 的函数实际上是接受 `a` 然后回传 `b -> c`，而 `b -> c` 实际上接受一个 `b` 然后回传一个 `c`。如果我们用比较少的参数调用一个函数，他就会回传一个函数需要接受剩下的参数。所以 `a -> b -> c` 可以写成 `a -> (b -> c)`。这样 curry 可以明显一些。

同样的，我们可以不要把 `fmap` 想成是一个接受 function 跟 functor 并回传一个 function 的 function。而是想成一个接受 function 并回传一个新的 function 的 function，回传的 function 接受一个 functor 并回传一个 functor。他接受 `a -> b` 并回传 `f a -> f b`。这动作叫做 lifting。我们用 GHCI 的 `:t` 来做的实验。

```haskell
ghci> :t fmap (*2)  
fmap (*2) :: (Num a, Functor f) => f a -> f a  
ghci> :t fmap (replicate 3)  
fmap (replicate 3) :: (Functor f) => f a -> f [a]
```

`fmap (*2)` 接受一个 functor `f`，并回传一个基于数字的 functor。那个 functor 可以是 list，可以是 `Maybe`，可以是 `Either String`。`fmap (replicate 3)` 可以接受一个基于任何型态的 functor，并回传一个基于 list 的 functor。

```
当我们提到 functor over numbers 的时候，你可以想像他是一个 functor 包含有许多数字在里面。前面一种说法其实比较正确，但后面一种说法比较容易让人理解。
```

这样的观察在我们只有绑定一个部份套用的函数，像是 `fmap (++"!")`，的时候会显得更清楚，

你可以把 `fmap` 想做是一个函数，他接受另一个函数跟一个 functor，然后把函数对 functor 每一个元素做映射，或你可以想做他是一个函数，他接受一个函数并把他 lift 到可以在 functors 上面操作。两种想法都是正确的，而且在 Haskell 中是等价。

`fmap (replicate 3) :: (Functor f) => f a -> f [a]` 这样的型态代表这个函数可以运作在任何 functor 上。至于确切的行为则要看究竟我们操作的是什么样的 functor。如果我们是用 `fmap (replicate 3)` 对一个 list 操作，那我们会选择 `fmap` 针对 list 的实作，也就是只是一个 `map`。如果我们是碰到 `Maybe a`。那他在碰到 `Just` 型态的时候，会对里面的值套用 `replicate 3`。而碰到 `Nothing` 的时候就回传 `Nothing`。

```haskell
ghci> fmap (replicate 3) [1,2,3,4]  
[[1,1,1],[2,2,2],[3,3,3],[4,4,4]]  
ghci> fmap (replicate 3) (Just 4)  
Just [4,4,4]  
ghci> fmap (replicate 3) (Right "blah")  
Right ["blah","blah","blah"]  
ghci> fmap (replicate 3) Nothing  
Nothing  
ghci> fmap (replicate 3) (Left "foo")  
Left "foo"
```

接下来我们来看看 functor laws。一个东西要成为 functor，必须要遵守某些定律。不管任何一个 functor 都被要求具有某些性质。他们必须是能被 map over 的。对他们调用 `fmap` 应该是要用一个函数 map 每一个元素，不多做任何事情。这些行为都被 functor laws 所描述。对于 `Functor` 的 instance 来说，总共两条定律应该被遵守。不过他们不会在 Haskell 中自动被检查，所以你必须自己确认这些条件。

functor law 的第一条说明，如果我们对 functor 做 map `id`，那得到的新的 functor 应该要跟原来的一样。如果写得正式一点，他代表 `fmap id = id`。基本上他就是说对 functor 调用 `fmap id`，应该等同于对 functor 调用 `id` 一样。毕竟 `id` 只是 identity function，他只会把参数照原样丢出。他也可以被写成 `\x -> x`。如果我们对 functor 的概念就是可以被 map over 的对象，那 `fmap id = id` 的性就显而易见。

我们来看看这个定律的几个案例：

```haskell
ghci> fmap id (Just 3)  
Just 3  
ghci> id (Just 3)  
Just 3  
ghci> fmap id [1..5]  
[1,2,3,4,5]  
ghci> id [1..5]  
[1,2,3,4,5]  
ghci> fmap id []  
[]  
ghci> fmap id Nothing  
Nothing
```

如果我们看看 `Maybe` 的 `fmap` 的实作，我们不难发现第一定律为何被遵守。

```haskell
instance Functor Maybe where  
    fmap f (Just x) = Just (f x)  
    fmap f Nothing = Nothing
```

我们可以想像在 `f` 的位置摆上 `id`。我们看到 `fmap id` 拿到 `Just x` 的时候，结果只不过是 `Just (id x)`，而 `id` 有只回传他拿到的东西，所以可以知道 `Just (id x)` 等价于 `Just x`。所以说我们可以知道对 `Maybe` 中的 `Just` 用 `id` 去做 map over 的动作，会拿回一样的值。

而将 `id` map over `Nothing` 会拿回 `Nothing` 并不稀奇。所以从这两个 `fmap` 的实作，我们可以看到的确 `fmap id = id` 有被遵守。

![](/files/-LjUmqmTq3f_ULRuvgXU)

*第二定律描述说先将两个函数合成并将结果 map over 一个 functor 的结果，应该跟先将第一个函数 map over 一个 functor，再将第二个函数 map over 那个 functor 的结果是一样的。*&#x6B63;式地写下来的话就是 `fmap (f . g) = fmap f . fmap g`。或是用另外一种写法，对于任何一个 functor F，下面这个式子应该要被遵守：`fmap (f . g) F = fmap f (fmap g F)`。

如果我们能够证明某个型别遵守两个定律，那我们就可以保证他跟其他 functor 对于映射方面都拥有相同的性质。我们知道如果对他用 `fmap`，我们知道不会有除了 mapping 以外的事会发生，而他就仅仅会表现成某个可以被 map over 的东西。也就是一个 functor。你可以再仔细查看 `fmap` 对于某些型别的实作来了解第二定律。正如我们先前对 `Maybe` 查看第一定律一般。

如果你需要的话，我们能在这边演练一下 `Maybe` 是如何遵守第二定律的。首先 `fmap (f . g)` 来 map over `Nothing` 的话，我们会得到 `Nothing`。因为用任何函数来 `fmap` `Nothing` 的话都会回传 `Nothing`。如果我们 `fmap f (fmap g Nothing)`，我们会得到 `Nothing`。可以看到当面对 `Nothing` 的时候，`Maybe` 很显然是遵守第二定律的。 那对于 `Just something` 呢？如果我们使用 `fmap (f . g) (Just x)` 的话，从实作的代码中我可以看到 `Just ((f . g ) x)`，也就是 `Just (f (g x))`。如果我们使用 `fmap f (fmap g (Just x))` 的话我们可以从实作知道 `fmap g (Just x)` 会是 `Just (g x)`。`fmap f (fmap g (Just x))` 跟 `fmap f (Just (g x))` 相等。而从实作上这又会相等于 `Just (f (g x))`。

如果你不太理解这边的说明，别担心。只要确定你了解什么是函数合成就好。在多数的情况下你可以直觉地对应到这些型别表现得就像 containers 或函数一样。或是也可以换种方法，只要多尝试对型别中不同的值做操作你就可以看看型别是否有遵守定律。

我们来看一些经典的例子。这些型别建构子虽然是 `Functor` 的 instance，但实际上他们并不是 functor，因为他们并不遵守这些定律。我们来看看其中一个型别。

```haskell
data CMaybe a = CNothing | CJust Int a deriving (Show)
```

C 这边代表的是计数器。他是一种看起来像是 `Maybe a` 的型别，只差在 `Just` 包含了两个 field 而不是一个。在 `CJust` 中的第一个 field 是 `Int`，他是扮演计数器用的。而第二个 field 则为型别 `a`，他是从型别参数来的，而他确切的型别当然会依据我们选定的 `CMaybe a` 而定。我们来对他作些操作来获得些操作上的直觉吧。

```haskell
ghci> CNothing  
CNothing  
ghci> CJust 0 "haha"  
CJust 0 "haha"  
ghci> :t CNothing  
CNothing :: CMaybe a  
ghci> :t CJust 0 "haha"  
CJust 0 "haha" :: CMaybe [Char]  
ghci> CJust 100 [1,2,3]  
CJust 100 [1,2,3]
```

如果我们使用 `CNothing`，就代表不含有 field。如果我们用的是 `CJust`，那第一个 field 是整数，而第二个 field 可以为任何型别。我们来定义一个 `Functor` 的 instance，这样每次我们使用 `fmap` 的时候，函数会被套用在第二个 field，而第一个 field 会被加一。

```haskell
instance Functor CMaybe where  
    fmap f CNothing = CNothing  
    fmap f (CJust counter x) = CJust (counter+1) (f x)
```

这种定义方式有点像是 `Maybe` 的定义方式，只差在当我们使用 `fmap` 的时候，如果碰到的不是空值，那我们不只会套用函数，还会把计数器加一。我们可以来看一些范例操作。

```haskell
ghci> fmap (++"ha") (CJust 0 "ho")  
CJust 1 "hoha"  
ghci> fmap (++"he") (fmap (++"ha") (CJust 0 "ho"))  
CJust 2 "hohahe"  
ghci> fmap (++"blah") CNothing  
CNothing
```

这些会遵守 functor laws 吗？要知道有不遵守的情形，只要找到一个反例就好了。

```haskell
ghci> fmap id (CJust 0 "haha")  
CJust 1 "haha"  
ghci> id (CJust 0 "haha")  
CJust 0 "haha"
```

我们知道 functor law 的第一定律描述当我们用 `id` 来 map over 一个 functor 的时候，他的结果应该跟只对 functor 调用 `id` 的结果一样。但我们可以看到这个例子中，这对于 `CMaybe` 并不遵守。尽管他的确是 `Functor` typeclass 的一个 instace。但他并不遵守 functor law 因此不是一个 functor。如果有人使用我们的 `CMaybe` 型别，把他当作 functor 用，那他就会期待 functor laws 会被遵守。但 `CMaybe` 并没办法满足，便会造成错误的程序。当我们使用一个 functor 的时候，函数合成跟 map over 的先后顺序不应该有影响。但对于 `CMaybe` 他是有影响的，因为他纪录了被 map over 的次数。如果我们希望 `CMaybe` 遵守 functor law，我们必须要让 `Int` 字段在做 `fmap` 的时候维持不变。

乍看之下 functor laws 看起来不是很必要，也容易让人搞不懂，但我们知道如果一个型别遵守 functor laws，那我们就能对他作些基本的假设。如果遵守了 functor laws，我们知道对他做 `fmap` 不会做多余的事情，只是用一个函数做映射而已。这让写出来的代码足够抽象也容易扩展。因为我们可以用定律来推论型别的行为。

所有在标准函式库中的 `Functor` 的 instance 都遵守这些定律，但你可以自己检查一遍。下一次你定义一个型别为 `Functor` 的 instance 的时候，花点时间确认他确实遵守 functor laws。一旦你操作过足够多的 functors 时，你就会获得直觉，知道他们会有什么样的性质跟行为。而且 functor laws 也会觉得显而易见。但就算没有这些直觉，你仍然可以一行一行地来找看看有没有反例让这些定律失效。

我们可以把 functor 看作输出具有 context 的值。例如说 `Just 3` 就是输出 `3`，但他又带有一个可能没有值的 context。`[1,2,3]` 输出三个值，`1`,`2` 跟 `3`，同时也带有可能有多个值或没有值的 context。`(+3)` 则会带有一个依赖于参数的 context。

如果你把 functor 想做是输出值这件事，那你可以把 map over 一个 functor 这件事想成在 functor 输出的后面再多加一层转换。当我们做 `fmap (+3) [1,2,3]` 的时候，我们是把 `(+3)` 接到 `[1,2,3]` 后面，所以当我们查看任何一个 list 的输出的时候，`(+3)` 也会被套用在上面。另一个例子是对函数做 map over。当我们做 `fmap (+3) (*3)`，我们是把 `(+3)` 这个转换套用在 `(*3)` 后面。这样想的话会很自然就会把 `fmap` 跟函数合成关联起来（`fmap (+3) (*3)` 等价于 `(+3) . (*3)`，也等价于 `\x -> ((x*3)+3)`），毕竟我们是接受一个函数 `(*3)` 然后套用 `(+3)` 转换。最后的结果仍然是一个函数，只是当我们喂给他一个数字的时候，他会先乘上三然后做转换加上三。这基本上就是函数合成在做的事。

## Applicative functors

![](/files/-LjUmuYB13LxbRcukGRN)

在这个章节中，我们会学到 applicative functors，也就是加强版的 functors，在 Haskell 中是用在 `Control.Applicative` 中的 `Applicative` 这个 typeclass 来定义的。

你还记得 Haskell 中函数缺省就是 Curried 的，那代表接受多个参数的函数实际上是接受一个参数然后回传一个接受剩余参数的函数，以此类推。如果一个函数的型别是 `a -> b -> c`，我们通常会说这个函数接受两个参数并回传 `c`，但他实际上是接受 `a` 并回传一个 `b -> c` 的函数。这也是为什么我们可以用 `(f x) y` 的方式调用 `f x y`。这个机制让我们可以 partially apply 一个函数，可以用比较少的参数调用他们。可以做成一个函数再喂给其他函数。

到目前为止，当我们要对 functor map over 一个函数的时候，我们用的函数都是只接受一个参数的。但如果我们要 map 一个接受两个参数的函数呢？我们来看几个具体的例子。如果我们有 `Just 3` 然后我们做 `fmap (*) (Just 3)`，那我们会获得什么样的结果？从 `Maybe` 对 `Functor` 的 instance 实作来看，我们知道如果他是 `Just something`，他会对在 `Just` 中的 `something` 做映射。因此当 `fmap (*) (Just 3)` 会得到 `Just ((*) 3)`，也可以写做 `Just (* 3)`。我们得到了一个包在 `Just` 中的函数。

```haskell
ghci> :t fmap (++) (Just "hey")  
fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])  
ghci> :t fmap compare (Just 'a')  
fmap compare (Just 'a') :: Maybe (Char -> Ordering)  
ghci> :t fmap compare "A LIST OF CHARS"  
fmap compare "A LIST OF CHARS" :: [Char -> Ordering]  
ghci> :t fmap (\x y z -> x + y / z) [3,4,5,6]  
fmap (\x y z -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]
```

如果我们 map `compare` 到一个包含许多字符的 list 呢？他的型别是 `(Ord a) => a -> a -> Ordering`，我们会得到包含许多 `Char -> Ordering` 型别函数的 list，因为 `compare` 被 partially apply 到 list 中的字符。他不是包含许多 `(Ord a) => a -> Ordering` 的函数，因为第一个 `a` 碰到的型别是 `Char`，所以第二个 `a` 也必须是 `Char`。

我们看到如何用一个多参数的函数来 map functor，我们会得到一个包含了函数的 functor。那现在我们能对这个包含了函数的 functor 做什么呢？我们能用一个吃这些函数的函数来 map over 这个 functor，这些在 functor 中的函数都会被当作参数丢给我们的函数。

```haskell
ghci> let a = fmap (*) [1,2,3,4]  
ghci> :t a  
a :: [Integer -> Integer]  
ghci> fmap (\f -> f 9) a  
[9,18,27,36]
```

但如果我们的有一个 functor 里面是 `Just (3 *)` 还有另一个 functor 里面是 `Just 5`，但我们想要把第一个 `Just (3 *)` map over `Just 5` 呢？如果是普通的 functor，那就没救了。因为他们只允许 map 一个普通的函数。即使我们用 `\f -> f 9` 来 map 一个装了很多函数的 functor，我们也是使用了普通的函数。我们是无法单纯用 `fmap` 来把包在一个 functor 的函数 map 另一个包在 functor 中的值。我们能用模式匹配 `Just` 来把函数从里面抽出来，然后再 map `Just 5`，但我们是希望有一个一般化的作法，对任何 functor 都有效。

我们来看看 `Applicative` 这个 typeclass。他位在 `Control.Applicative` 中，在其中定义了两个函数 `pure` 跟 `<*>`。他并没有提供缺省的实作，如果我们想使用他必须要为他们 applicative functor 的实作。typeclass 定义如下：

```haskell
class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b
```

这简简单单的三行可以让我们学到不少。首先来看第一行。他开启了 `Applicative` 的定义，并加上 class contraint。描述了一个型别构造子要是 `Applicative`，他必须也是 `Functor`。这就是为什么我们说一个型别构造子属于 `Applicative` 的话，他也会是 `Functor`，因此我们能对他使用 `fmap`。

第一个定义的是 `pure`。他的型别宣告是 `pure :: a -> f a`。`f` 代表 applicative functor 的 instance。由于 Haskell 有一个优秀的型别系统，其中函数又是将一些参数映射成结果，我们可以从型别宣告中读出许多消息。`pure` 应该要接受一个值，然后回传一个包含那个值的 applicative functor。我们这边是用盒子来作比喻，即使有一些比喻不完全符合现实的情况。尽管这样，`a -> f a` 仍有许多丰富的信息，他确实告诉我们他会接受一个值并回传一个 applicative functor，里面装有结果。

对于 `pure` 比较好的说法是把一个普通值放到一个缺省的 context 下，一个最小的 context 但仍然包含这个值。

`<*>` 也非常有趣。他的型别是 `f (a -> b) -> f a -> f b`。这有让你联想到什么吗？没错！就是 `fmap :: (a -> b) -> f a -> f b`。他有点像加强版的 `fmap`。然而 `fmap` 接受一个函数跟一个 functor，然后套用 functor 之中的函数。`<*>` 则是接受一个装有函数的 functor 跟另一个 functor，然后取出第一个 functor 中的函数将他对第二个 functor 中的值做 map。

我们来看看 `Maybe` 的 `Applicative` 实作：

```haskell
instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something
```

从 class 的定义我们可以看到 `f` 作为 applicative functor 会接受一个具体型别当作参数，所以我们是写成 `instance Applicative Maybe where` 而不是写成 `instance Applicative (Maybe a) where`。

首先看到 `pure`。他只不过是接受一个东西然后包成 applicative functor。我们写成 `pure = Just` 是因为 `Just` 不过就是一个普通函数。我们其实也可以写成 `pure x = Just x`。

接着我们定义了 `<*>`。我们无法从 `Nothing` 中抽出一个函数，因为 `Nothing` 并不包含一个函数。所以我们说如果我们要尝试从 `Nothing` 中取出一个函数，结果必定是 `Nothing`。如果你看看 `Applicative` 的定义，你会看到他有 `Functor` 的限制，他代表 `<*>` 的两个参数都会是 functors。如果第一个参数不是 `Nothing`，而是一个装了函数的 `Just`，而且我们希望将这个函数对第二个参数做 map。这个也考虑到第二个参数是 `Nothing` 的情况，因为 `fmap` 任何一个函数至 `Nothing` 会回传 `Nothing`。

对于 `Maybe` 而言，如果左边是 `Just`，那 `<*>` 会从其中抽出了一个函数来 map 右边的值。如果有任何一个参数是 `Nothing`。那结果便是 `Nothing`。

来试试看吧！

```haskell
ghci> Just (+3) <*> Just 9  
Just 12  
ghci> pure (+3) <*> Just 10  
Just 13  
ghci> pure (+3) <*> Just 9  
Just 12  
ghci> Just (++"hahah") <*> Nothing  
Nothing  
ghci> Nothing <*> Just "woot"  
Nothing
```

我们看到 `pure (+3)` 跟 `Just (+3)` 在这个 case 下是一样的。如果你是在 applicative context 底下跟 `Maybe` 打交道的话请用 `pure`，要不然就用 `Just`。前四个输入展示了函数是如何被取出并做 map 的动作，但在这个 case 底下，他们同样也可以用 unwrap 函数来 map over functors。最后一行比较有趣，因为我们试着从 `Nothing` 取出函数并将他 map 到某个值。结果当然是 `Nothing`。

对于普通的 functors，你可以用一个函数 map over 一个 functors，但你可能没办法拿到结果。而 applicative functors 则让你可以用单一一个函数操作好几个 functors。看看下面一段代码：

```haskell
ghci> pure (+) <*> Just 3 <*> Just 5  
Just 8  
ghci> pure (+) <*> Just 3 <*> Nothing  
Nothing  
ghci> pure (+) <*> Nothing <*> Just 5  
Nothing
```

![](/files/-MlVFmpEDwTHZUg_EDsv)

究竟我们写了些什么？我们来一步步看一下。`<*>` 是 left-associative，也就是说 `pure (+) <*> Just 3 <*> Just 5` 可以写成 `(pure (+) <*> Just 3) <*> Just 5`。首先 `+` 是摆在一个 functor 中，在这边刚好他是一个 `Maybe`。所以首先，我们有 `pure (+)`，他等价于 `Just (+)`。接下来由于 partial application 的关系，`Just (+) <*> Just 3` 等价于 `Just (3+)`。把一个 `3` 喂给 `+` 形成另一个只接受一个参数的函数，他的效果等于加上 3。最后 `Just (3+) <*> Just 5` 被运算，其结果是 `Just 8`。

这样很棒吧！用 applicative style 的方式来使用 applicative functors。像是 `pure f <*> x <*> y <*> ...` 就让我们可以拿一个接受多个参数的函数，而且这些参数不一定是被包在 functor 中。就这样来套用在多个在 functor context 的值。这个函数可以吃任意多的参数，毕竟 `<*>` 只是做 partial application 而已。

如果我们考虑到 `pure f <*> x` 等于 `fmap f x` 的话，这样的用法就更方便了。这是 applicative laws 的其中一条。我们稍后会更仔细地查看这条定律。现在我们先依直觉来使用他。就像我们先前所说的，`pure` 把一个值放进一个缺省的 context 中。如果我们要把一个函数放在一个缺省的 context，然后把他取出并套用在放在另一个 applicative functor 的值。我们会做的事就是把函数 map over 那个 applicative functor。但我们不会写成 `pure f <*> x <*> y <*> ...`，而是写成 `fmap f x <*> y <*> ...`。这也是为什么 `Control.Applicative` 会 export 一个函数 `<$>`，他基本上就是中缀版的 `fmap`。他是这么被定义的：

```haskell
(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x
```

```
要记住型别变量跟参数的名字还有值绑定的名称不冲突。``f`` 在函数的型别宣告中是型别变量，说明 ``f`` 应该要满足 ``Functor`` typeclass 的条件。而在函数本体中的 ``f`` 则表示一个函数，我们将他 map over x。我们同样用 ``f`` 来表示他们并代表他们是相同的东西。
```

`<$>` 的使用显示了 applicative style 的好处。如果我们想要将 `f` 套用三个 applicative functor。我们可以写成 `f <$> x <*> y <*> z`。如果参数不是 applicative functor 而是普通值的话。我们则写成 `f x y z`。

我们再仔细看看他是如何运作的。我们有一个 `Just "johntra"` 跟 `Just "volta"` 这样的值，我们希望将他们结合成一个 `String`，并且包含在 `Maybe` 中。我们会这样做：

```haskell
ghci> (++) <$> Just "johntra" <*> Just "volta"  
Just "johntravolta"
```

可以将上面的跟下面这行比较一下：

```haskell
ghci> (++) "johntra" "volta"  
"johntravolta"
```

可以将一个普通的函数套用在 applicative functor 上真不错。只要稍微写一些 `<$>` 跟 `<*>` 就可以把函数变成 applicative style，可以操作 applicatives 并回传 applicatives。

总之当我们在做 `(++) <$> Just "johntra" <*> Just "volta"` 时，首先我们将 `(++)` map over 到 `Just "johntra"`，然后产生 `Just ("johntra"++)`，其中 `(++)` 的型别为 `(++) :: [a] -> [a] -> [a]`，`Just ("johntra"++)` 的型别为 `Maybe ([Char] -> [Char])`。注意到 `(++)` 是如何吃掉第一个参数，以及我们是怎么决定 `a` 是 `Char` 的。当我们做 `Just ("johntra"++) <*> Just "volta"`，他接受一个包在 `Just` 中的函数，然后 map over `Just "volta"`，产生了 `Just "johntravolta"`。如果两个值中有任意一个为 `Nothing`，那整个结果就会是 `Nothing`。

到目前为止我们只有用 `Maybe` 当作我们的案例，你可能也会想说 applicative functor 差不多就等于 `Maybe`。不过其实有许多其他 `Applicative` 的 instance。我们来看看有哪些。

List 也是 applicative functor。很惊讶吗？来看看我们是怎么定义 `[]` 为 `Applicative` 的 instance 的。

```haskell
instance Applicative [] where  
    pure x = [x]  
    fs <*> xs = [f x | f <- fs, x <- xs]
```

早先我们说过 `pure` 是把一个值放进缺省的 context 中。换种说法就是一个会产生那个值的最小 context。而对 list 而言最小 context 就是 `[]`，但由于空的 list 并不包含一个值，所以我们没办法把他当作 `pure`。这也是为什么 `pure` 其实是接受一个值然后回传一个包含单元素的 list。同样的，`Maybe` 的最小 context 是 `Nothing`，但他其实表示的是没有值。所以 `pure` 其实是被实作成 `Just` 的。

```haskell
ghci> pure "Hey" :: [String]  
["Hey"]  
ghci> pure "Hey" :: Maybe String  
Just "Hey"
```

至于 `<*>` 呢？如果我们假定 `<*>` 的型别是限制在 list 上的话，我们会得到 `(<*>) :: [a -> b] -> [a] -> [b]`。他是用 list comprehension 来实作的。`<*>` 必须要从左边的参数取出函数，将他 map over 右边的参数。但左边的 list 有可能不包含任何函数，也可能包含一个函数，甚至是多个函数。而右边的 list 有可能包含多个值。这也是为什么我们用 list comprehension 的方式来从两个 list 取值。我们要对左右任意的组合都做套用的动作。而得到的结果就会是左右两者任意组合的结果。

```haskell
ghci> [(*0),(+100),(^2)] <*> [1,2,3]  
[0,0,0,101,102,103,1,4,9]
```

左边的 list 包含三个函数，而右边的 list 有三个值。所以结果会是有九个元素的 list。在左边 list 中的每一个函数都被套用到右边的值。如果我们今天在 list 中的函数是接收两个参数的，我们也可以套用到两个 list 上。

```haskell
ghci> [(+),(*)] <*> [1,2] <*> [3,4]  
[4,5,5,6,3,4,6,8]
```

由于 `<*>` 是 left-associative，也就是说 `[(+),(*)] <*> [1,2]` 会先运作，产生 `[(1+),(2+),(1*),(2*)]`。由于左边的每一个函数都套用至右边的每一个值。也就产生 `[(1+),(2+),(1*),(2*)] <*> [3,4]`，其便是最终结果。

list 的 applicative style 是相当有趣的：

```haskell
ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."]  
["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."]
```

看看我们是如何将一个接受两个字串参数的函数套用到两个 applicative functor 上的，只要用适当的 applicative 运算子就可以达成。

你可以将 list 看作是一个 non-deterministic 的计算。而对于像 `100` 或是 `"what"` 这样的值则是 deterministic 的计算，只会有一个结果。而 `[1,2,3]` 则可以看作是没有确定究竟是哪一种结果。所以他代表的是所有可能的结果。当你在做 `(+) <$> [1,2,3] <*> [4,5,6]`，你可以想做是把两个 non-deterministic 的计算做 `+`，只是他会产生另一个 non-deterministic 的计算，而且结果更加不确定。

Applicative style 对于 list 而言是一个取代 list comprehension 的好方式。在第二章中，我们想要看到 `[2,5,10]` 跟 `[8,10,11]` 相乘的结果，所以我们这样做：

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

我们只是从两个 list 中取出元素，并将一个函数套用在任何元素的组合上。这也可以用 applicative style 的方式来写：

```haskell
ghci> (*) <$> [2,5,10] <*> [8,10,11]  
[16,20,22,40,50,55,80,100,110]
```

这写法对我来说比较清楚。可以清楚表达我们是要对两个 non-deterministic 的计算做 `*`。如果我们想要所有相乘大于 50 可能的计算结果，我们会这样写：

```haskell
ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]  
[55,80,100,110]
```

很容易看到 `pure f <*> xs` 等价于 `fmap f xs`。而 `pure f` 就是 `[f]`，而且 `[f] <*> xs` 可将左边的每个函数套用至右边的每个值。但左边其实只有一个函数，所以他做起来就像是 mapping。

另一个我们已经看过的 `Applicative` 的 instance 是 `IO`，来看看他是怎么实作的：

```haskell
instance Applicative IO where  
    pure = return  
    a <*> b = do  
        f <- a  
        x <- b  
        return (f x)
```

![](/files/-MlVFmpIMAHIeTn_-1fZ)

由于 `pure` 是把一个值放进最小的 context 中，所以将 `return` 定义成 `pure` 是很合理的。因为 `return` 也是做同样的事情。他做了一个不做任何事情的 I/O action，他可以产生某些值来作为结果，但他实际上并没有做任何 I/O 的动作，例如说印出结果到终端或是文件。

如果 `<*>` 被限定在 `IO` 上操作的话，他的型别会是 `(<*>) :: IO (a -> b) -> IO a -> IO b`。他接受一个产生函数的 I/O action，还有另一个 I/O action，并从以上两者创造一个新的 I/O action，也就是把第二个参数喂给第一个参数。而得到回传的结果，然后放到新的 I/O action 中。我们用 do 的语法来实作他。你还记得的话 do 就是把好几个 I/O action 黏在一起，变成一个大的 I/O action。

而对于 `Maybe` 跟 `[]` 而言，我们可以把 `<*>` 想做是从左边的参数取出一个函数，然后套用到右边的参数上。至于 `IO`，这种取出的模拟方式仍然适用，但我们必须多加一个 sequencing 的概念，因为我们是从两个 I/O action 中取值，也是在 sequencing，把他们黏成一个。我们从第一个 I/O action 中取值，但要取出 I/O action 的结果，他必须要先被执行过。

考虑下面这个范例：

```haskell
myAction :: IO String  
myAction = do  
    a <- getLine  
    b <- getLine  
    return $ a ++ b
```

这是一个提示用户输入两行并产生将两行输入串接在一起结果的一个 I/O action。我们先把两个 `getLine` 黏在一起，然后用一个 `return`，这是因为我们想要这个黏成的 I/O action 包含 `a ++ b` 的结果。我们也可以用 applicative style 的方式来描述：

```haskell
myAction :: IO String  
myAction = (++) <$> getLine <*> getLine
```

我们先前的作法是将两个 I/O action 的结果喂给函数。还记得 `getLine` 的型别是 `getLine :: IO String`。当我们对 applicative functor 使用 `<*>` 的时候，结果也会是 applicative functor。

如果我们再使用盒子的模拟，我们可以把 `getLine` 想做是一个去真实世界中拿取字串的盒子。而 `(++) <$> getLine <*> getLine` 会创造一个比较大的盒子，这个大盒子会派两个盒子去终端拿取字串，并把结果串接起来放进自己的盒子中。

`(++) <$> getLine <*> getLine` 的型别是 `IO String`，他代表这个表达式式一个再普通不过的 I/O action，他里面也装着某种值。这也是为什么我们可以这样写：

```haskell
main = do  
    a <- (++) <$> getLine <*> getLine  
    putStrLn $ "The two lines concatenated turn out to be: " ++ a
```

如果你发现你是在做 binding I/O action 的动作，而且在 binding 之后还调用一些函数，最后用 `return` 来将结果包起来。 那你可以考虑使用 applicative style，这样可以更简洁。

另一个 `Applicative` 的 instance 是 `(->) r`。虽然他们通常是用在 code golf 的情况，但他们还是十分有趣的例子。所以我们还是来看一下他们是怎么被实作的。

```
如果你忘记 ``(->) r`` 的意思，回去翻翻前一章节我们介绍 ``(->) r`` 作为一个 functor 的范例。
```

```haskell
instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x)
```

当我们用 `pure` 将一个值包成 applicative functor 的时候，他产生的结果永远都会是那个值。也就是最小的 context。那也是为什么对于 function 的 `pure` 实作来讲，他就是接受一个值，然后造一个函数永远回传那个值，不管他被喂了什么参数。如果你限定 `pure` 的型别至 `(->) r` 上，他就会是 `pure :: a -> (r -> a)`。

```haskell
ghci> (pure 3) "blah"  
3
```

由于 currying 的关系，函数套用是 left-associative，所以我们忽略掉括弧。

```haskell
ghci> pure 3 "blah"  
3
```

而 `<*>` 的实作是比较不容易了解的，我们最好看一下怎么用 applicative style 的方式来使用作为 applicative functor 的 function。

```haskell
ghci> :t (+) <$> (+3) <*> (*100)  
(+) <$> (+3) <*> (*100) :: (Num a) => a -> a  
ghci> (+) <$> (+3) <*> (*100) $ 5  
508
```

将两个 applicative functor 喂给 `<*>` 可以产生一个新的 applicative functor，所以如果我们丢给他两个函数，我们能得到一个新的函数。所以是怎么一回事呢？当我们做 `(+) <$> (+3) <*> (*100)`，我们是在实作一个函数，他会将 `(+3)` 跟 `(*100)` 的结果再套用 `+`。要看一个实际的范例的话，可以看一下 `(+) <$> (+3) <*> (*100) $ 5` 首先 `5` 被丢给 `(+3)` 跟 `(*100)`，产生 `8` 跟 `500`。然后 `+` 被套用到 `8` 跟 `500`，得到 `508`。

```haskell
ghci> (\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5  
[8.0,10.0,2.5]
```

![](/files/-MlVFmpMm3SA5zDhGc2N)

这边也一样。我们创建了一个函数，他会调用 `\x y z -> [x,y,z]`，而丢的参数是 `(+3)`, `(*2)` 跟 `(/2)`。`5` 被丢给以上三个函数，然后他们结果又接到 `\x y z -> [x, y, z]`。

你可以将函数想做是装着最终结果的盒子，所以 `k <$> f <*> g` 会制造一个函数，他会将 `f` 跟 `g` 的结果丢给 `k`。当我们做 `(+) <$> Just 3 <*> Just 5`，我们是用 `+` 套用在一些可能有或可能没有的值上，所以结果也会是可能有或没有。当我们做 `(+) <$> (+10) <*> (+5)`，我们是将 `+` 套用在 `(+10)` 跟 `(+5)` 的结果上，而结果也会是一个函数，当被喂给一个参数的时候会产生结果。

我们通常不会将函数当作 applicative 用，不过仍然值得当作练习。对于 `(->) r` 怎么定义成 `Applicative` 的并不是真的那么重要，所以如果你不是很懂的话也没关系。这只是让你获得一些操作上的直觉罢了。

一个我们之前还没碰过的 `Applicative` 的 instance 是 `ZipList`，他是包含在 `Control.Applicative` 中。

对于 list 要作为一个 applicative functor 可以有多种方式。我们已经介绍过其中一种。如果套用 `<*>`，左边是许多函数，而右边是许多值，那结果会是函数套用到值的所有组合。如果我们做 `[(+3),(*2)] <*> [1,2]`。那 `(+3)` 会先套用至 `1` 跟 `2`。接着 `(*2)` 套用至 `1` 跟 `2`。而得到 `[4,5,2,4]`。

然而 `[(+3),(*2)] <*> [1,2]` 也可以这样运作:把左边第一个函数套用至右边第一个值，接着左边第二个函数套用右边第二个值，以此类推。这样得到的会是 `[4,4]`。或是 `[1 + 3, 2 * 2]`。

由于一个型别不能对同一个 typeclass 定义两个 instance，所以才会定义了 `ZipList a`，他只有一个构造子 `ZipList`，他只包含一个字段，他的型别是 list。

```haskell
instance Applicative ZipList where  
        pure x = ZipList (repeat x)  
        ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)
```

`<*>` 做的就是我们之前说的。他将第一个函数套用至第一个值，第二个函数套用第二个值。这也是 `zipWith (\f x -> f x) fs xs` 做的事。由于 `zipWith` 的特性，所以结果会跟 list 中比较短的那个一样长。

`pure` 也值得我们讨论一下。他接受一个值，把他重复地放进一个 list 中。`pure "haha"` 就会是 `ZipList (["haha","haha","haha"...`。这可能会造成些混淆，毕竟我们说过 `pure` 是把一个值放进一个最小的 context 中。而你会想说无限长的 list 不可能会是一个最小的 context。但对于 zip list 来说这是很合理的，因为他必须在 list 的每个位置都有值。这也遵守了 `pure f <*> xs` 必须要等价于 `fmap f xs` 的特性。如果 `pure 3` 只是回传 `ZipList [3]`，那 `pure (*2) <*> ZipList [1,5,10]` 就只会算出 `ZipList [2]`，因为两个 zip list 算出结果的长度会是比较短的那个的长度。如果我们 zip 一个有限长的 list 以及一个无限长的 list，那结果的长会是有限长的 list 的长度。

那 zip list 是怎么用 applicative style 操作的呢？我们来看看，`ZipList a` 型别并没有定义成 `Show` 的 instance，所以我们必须用 `getZipList` 函数来从 zip list 取出一个普通的 list。

```haskell
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]  
[101,102,103]  
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..]  
[101,102,103]  
ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]  
[5,3,3,4]  
ghci> getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat"  
[('d','c','r'),('o','a','a'),('g','t','t')]
```

```
``(,,)`` 函数跟 ``\x y z -> (x,y,z)`` 是等价的，而 ``(,)`` 跟 ``\x y -> (x,y)`` 是等价的。
```

除了 `zipWith`，标准函式库中也有 `zipWith3`, `zipWith4` 之类的函数，最多支持到 7。`zipWith` 接受一个接受两个参数的函数，并把两个 list zip 起来。`zipWith3` 则接受一个接受三个参数的函数，然后把三个 list zip 起来。以此类推。用 applicative style 的方式来操作 zip list 的话，我们就不需要对每个数量的 list 都定义一个独立的 zip 函数来 zip 他们。我们只需要用 applicative style 的方式来把任意数量的 list zip 起来就可以了。

`Control.Applicative` 定义了一个函数叫做 `liftA2`，他的型别是 `liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c`。他定义如下：

```haskell
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c  
liftA2 f a b = f <$> a <*> b
```

并没有太难理解的东西，他不过就是对两个 applicatives 套用函数而已，而不用我们刚刚熟悉的 applicative style。我们提及他的理由只是要展示为什么 applicative functors 比起一般的普通 functor 要强。如果只是普通的 functor 的话，我们只能将一个函数 map over 这个 functor。但有了 applicative functor，我们可以对好多个 functor 套用一个函数。看看这个函数的型别，他会是 `(a -> b -> c) -> (f a -> f b -> f c)`。当我们从这样的角度来看他的话，我们可以说 `liftA2` 接受一个普通的二元函数，并将他升级成一个函数可以运作在两个 functor 之上。

另外一个有趣的概念是，我们可以接受两个 applicative functor 并把他们结合成一个 applicative functor，这个新的将这两个 applicative functor 装在 list 中。举例来说，我们现在有 `Just 3` 跟 `Just 4`。我们假设后者是一个只包含单元素的 list。

```haskell
ghci> fmap (\x -> [x]) (Just 4)  
Just [4]
```

所以假设我们有 `Just 3` 跟 `Just [4]`。我们有怎么得到 `Just [3,4]` 呢？很简单。

```haskell
ghci> liftA2 (:) (Just 3) (Just [4])  
Just [3,4]  
ghci> (:) <$> Just 3 <*> Just [4]  
Just [3,4]
```

还记得 `:` 是一个函数，他接受一个元素跟一个 list，并回传一个新的 list，其中那个元素已经接在前面。现在我们有了 `Just [3,4]`，我们能够将他跟 `Just 2` 绑在一起变成 `Just [2,3,4]` 吗？当然可以。我们可以将任意数量的 applicative 绑在一起变成一个 applicative，里面包含一个装有结果的 list。我们试着实作一个函数，他接受一串装有 applicative 的 list，然后回传一个 applicative 里面有一个装有结果的 list。我们称呼他为 `sequenceA`。

```haskell
sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
```

居然用到了递归！首先我们来看一下他的型别。他将一串 applicative 的 list 转换成一个 applicative 装有一个 list。从这个信息我们可以推测出边界条件。如果我们要将一个空的 list 变成一个装有 list 的 applicative。我们只要把这个空的 list 放进一个缺省的 context。现在来看一下我们怎么用递归的。如果们有一个可以分成头跟尾的 list（`x` 是一个 applicative 而 `xs` 是一串 applicatve），我们可以对尾巴调用 `sequenceA`，便会得到一个装有 list 的 applicative。然后我们只要将在 `x` 中的值把他接到装有 list 的 applicative 前面就可以了。

所以如果我们做 `sequenceA [Just 1, Just 2]`，也就是 `(:) <$> Just 1 <*> sequenceA [Just 2]`。那会等价于 `(:) <$> Just 1 <*> ((:) <$> Just 2 <*> sequenceA [])`。我们知道 `sequenceA []` 算出来会是 `Just []`，所以运算式就变成 `(:) <$> Just 1 <*> ((:) <$> Just 2 <*> Just [])`，也就是 `(:) <$> Just 1 <*> Just [2]`，算出来就是 `Just [1,2]`。

另一种实作 `sequenceA` 的方式是用 fold。要记得几乎任何需要走遍整个 list 并 accumulate 成一个结果的都可以用 fold 来实作。

```haskell
sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA = foldr (liftA2 (:)) (pure [])
```

我们从右往左走，并且起始的 accumulator 是用 `pure []`。我们是用 `liftA2 (:)` 来结合 accumulator 跟 list 中最后的元素，而得到一个 applicative，里面装有一个单一元素的一个 list。然后我们再用 `liftA2 (:)` 来结合 accumulator 跟最后一个元素，直到我们只剩下 accumulator 为止，而得到一个 applicative，里面装有所有结果。

我们来试试看套用在不同 applicative 上。

```haskell
ghci> sequenceA [Just 3, Just 2, Just 1]  
Just [3,2,1]  
ghci> sequenceA [Just 3, Nothing, Just 1]  
Nothing  
ghci> sequenceA [(+3),(+2),(+1)] 3  
[6,5,4]  
ghci> sequenceA [[1,2,3],[4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> sequenceA [[1,2,3],[4,5,6],[3,4,4],[]]  
[]
```

很酷吧。当我们套用在 `Maybe` 上时，`sequenceA` 创造一个新的 `Maybe`，他包含了一个 list 装有所有结果。如果其中一个值是 `Nothing`，那整个结果就会是 `Nothing`。如果你有一串 `Maybe` 型别的值，但你只在乎当结果不包含任何 `Nothing` 的情况，这样的特性就很方便。

当套用在函数时，`sequenceA` 接受装有一堆函数的 list，并回传一个回传 list 的函数。在我们的范例中，我们写了一个函数，他只接受一个数值作为参数，他会把他套用至 list 中的每一个函数，并回传一个包含结果的 list。`sequenceA [(+3),(+2),(+1)] 3` 会将 `3` 喂给 `(+3)`, `(+2)` 跟 `(+1)`，然后将所有结果装在一个 list 中。

而 `(+) <$> (+3) <*> (*2)` 会创见一个接受单一参数的一函数，将他同时喂给 `(+3)` 跟 `(*2)`，然后调用 `+` 来将两者加起来。同样的道理，`sequenceA [(+3),(*2)]` 是制造一个接受单一参数的函数，他会将他喂给所有包含在 list 中的函数。但他最后不是调用 `+`，而是调用 `:` 跟 `pure []` 来把结果接成一个 list，得到最后的结果。

当我们有一串函数，我们想要将相同的输入都喂给他们并查看结果的时候，`sequenceA` 非常好用。例如说，我们手上有一个数值，但不知道他是否满足一串 predicate。一种实作的方式是像这样：

```haskell
ghci> map (\f -> f 7) [(>4),(<10),odd]  
[True,True,True]  
ghci> and $ map (\f -> f 7) [(>4),(<10),odd]  
True
```

记住 `and` 接受一串布尔值，并只有在全部都是 `True` 的时候才回传 `True`。 另一种实作方式是用 `sequenceA`：

```haskell
ghci> sequenceA [(>4),(<10),odd] 7  
[True,True,True]  
ghci> and $ sequenceA [(>4),(<10),odd] 7  
True
```

`sequenceA [(>4),(<10),odd]` 接受一个函数，他接受一个数值并将他喂给所有的 predicate，包含 `[(>4),(<10),odd]`。然后回传一串布尔值。他将一个型别为 `(Num a) => [a -> Bool]` 的 list 变成一个型别为 `(Num a) => a -> [Bool]` 的函数，很酷吧。

由于 list 要求里面元素的型别要一致，所以包含在 list 中的所有函数都是同样型别。你不能创造一个像是 `[ord, (+3)]` 这样的 list，因为 `ord` 接受一个字符并回传一个数值，然而 `(+3)` 接受一个数值并回传一个数值。

当跟 `[]` 一起使用的时候，`sequenceA` 接受一串 list，并回传另一串 list。他实际上是创建一个包含所有可能组合的 list。为了方便说明，我们比较一下使用 `sequenceA` 跟 list comprehension 的差异：

```haskell
ghci> sequenceA [[1,2,3],[4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> [[x,y] | x <- [1,2,3], y <- [4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> sequenceA [[1,2],[3,4]]  
[[1,3],[1,4],[2,3],[2,4]]  
ghci> [[x,y] | x <- [1,2], y <- [3,4]]  
[[1,3],[1,4],[2,3],[2,4]]  
ghci> sequenceA [[1,2],[3,4],[5,6]]  
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]  
ghci> [[x,y,z] | x <- [1,2], y <- [3,4], z <- [5,6]]  
[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
```

这可能有点难以理解，但如果你多做点尝试，你会比较能看出来些眉目。假设我们在做 `sequenceA [[1,2],[3,4]]`。要知道这是怎么回事，我们首先用 `sequenceA` 的定义 `sequenceA (x:xs) = (:) <$> x <*> sequenceA xs` 还有边界条件 `sequenceA [] = pure []` 来看看。你不需要实际计算，但他可以帮助你理解 `sequenceA` 是怎么运作在一串 list 上，毕竟这有点复杂。

```
# 我们从 ``sequenceA [[1,2],[3,4]]`` 开始
# 那可以被计算成 ``(:) <$> [1,2] <*> sequenceA [[3,4]]``
# 计算内层的 ``sequenceA``，会得到 ``(:) <$> [1,2] <*> ((:) <$> [3,4] <*> sequenceA [])``
# 我们碰到了边界条件，所以会是 ``(:) <$> [1,2] <*> ((:) <$> [3,4] <*> [[]])``
# 现在我们计算 ``(:) <$> [3,4] <*> [[]] `` 的部份，我们会对左边 list 中的每一个值 (也就是 ``3`` 跟 ``4``) 跟右边的每一个值 (只有 ``[]``)套用 ``:``，而得到 ``[3:[], 4:[]]``，也就是 ``[[3],[4]]``。所以我们有 ``(:) <$> [1,2] <*> [[3],[4]]``
# 而对于左边的每一个值(``1`` 跟 ``2``)以及右边可能的值（``[3]`` 跟 ``[4]``）我们套用 ``:`` 而得到 ``[1:[3], 1:[4], 2:[3], 2:[4]]``，他等于 ``[[1,3],[1,4],[2,3],[2,4]]``
```

计算 `(+) <$> [1,2] <*> [4,5,6]` 会得到一个 non-deterministic 的结果 `x + y`，其中 `x` 代表 `[1,2]` 中的每一个值，而 `y` 代表 `[4,5,6]` 中的每一个值。我们用 list 来表示每一种可能的情形。同样的，当我们在做 `sequence [[1,2],[3,4],[5,6],[7,8]]`，他的结果会是 non-deterministic 的 `[x,y,z,w]`，其中 `x` 代表 `[1,2]` 中的每一个值，而 `y` 代表 `[3,4]` 中的每一个值。以此类推。我们用 list 代表 non-deterministic 的计算，每一个元素都是一个可能的情形。这也是为什么会用到 list of list。

当使用在 I/O action 上的时候，`sequenceA` 跟 `sequence` 是等价的。他接受一串 I/O action 并回传一个 I/O action，这个 I/O action 会计算 list 中的每一个 I/O action，并把结果放在一个 list 中。要将型别为 `[IO a]` 的值转换成 `IO [a]` 的值，也就是会产生一串 list 的一个 I/O action，那这些 I/O action 必须要一个一个地被计算，毕竟对于这些 I/O action 你没办法不计算就得到结果。

```haskell
ghci> sequenceA [getLine, getLine, getLine]  
heyh  
ho  
woo  
["heyh","ho","woo"]
```

就像普通的函数一样，applicative functors 也遵循一些定律。其中最重要的一个是我们之前提过的 `pure f <*> x = fmap f x`。你可以证明一些我们之前介绍过的 applicative functor 遵守这个定律当作练习。其他的 functors law 有：

```
# ``pure id <*> v = v``
# ``pure (.) <*> u <*> v <*> w = u <*> (v <*> w)``
# ``pure f <*> pure x = pure (f x)``
# ``u <*> pure y = pure ($ y) <*> u``
```

我们不会一项一项地细看，那样会花费很大的篇幅而且对读者来说很无聊，但如果你有兴趣，你可以针对某些 instance 看看他们会不会遵守。

结论就是 applicative functor 不只是有趣而且实用， 他允许我们结合不同种类的计算，像是 I/O 计算，non-deterministic 的计算，有可能失败的计算等等。而使用 `<$>` 跟 `<*>` 我们可以将普通的函数来运作在任意数量的 applicative functors 上。

## 关键字"newtype"

![](https://github.com/MnO2/learnyouahaskell-zh/tree/e47e717ed1af92c4e38a596af5347e7d1434c454/zh-cn/ch11/maoi.png)

到目前为止，我们已经看过了如何用 `data` 关键字定义自己的 algebraic data type。我们也学习到了如何用 `type` 来定义 type synonyms。在这个章节中，我们会看一下如何使用 `newtype` 来从一个现有的型别中定义出新的型别，并说明我们为什么会想要那么做。

在之前的章节中，我们了解到其实 list 有很多种方式可以被视为一种 applicative functor。一中方式是定义 `<*>` 将左边的每一个值跟右边的每一个值组合，而得到各种组合的结果。

```haskell
ghci> [(+1),(*100),(*5)] <*> [1,2,3]  
[2,3,4,100,200,300,5,10,15]
```

第二种方式是将 `<*>` 定义成将左边的第一个函数套用至右边的第一个值，然后将左边第二个函数套用至右边第二个值。以此类推。最终，这表现得有点像将两个 list 用一个拉链拉起来一样。但由于 list 已经被定义成 `Applicaitive` 的 instance 了，所以我们要怎么要让 list 可以被定义成第二种方式呢？如果你还记得我们说过我们是有很好的理由定义了 `ZipList a`，其中他里面只包含一个值构造子跟只包含一个字段。其实他的理由就是要让 `ZipList` 定义成用拉链的方式来表现 applicative 行为。我们只不过用 `ZipList` 这个构造子将他包起来，然后用 `getZipList` 来解开来。

```haskell
ghci> getZipList $ ZipList [(+1),(*100),(*5)] <*> ZipList [1,2,3]  
[2,200,15]
```

所以这跟 newtype 这个关键字有什么关系呢？想想看我们是怎么宣告我们的 `ZipList a` 的，一种方式是像这样：

```haskell
data ZipList a = ZipList [a]
```

也就是一个只有一个值构造子的型别而且那个构造子里面只有一个字段。我们也可以用 record syntax 来定义一个解开的函数：

```haskell
data ZipList a = ZipList { getZipList :: [a] }
```

这样听起来不错。这样我们就有两种方式来让一个型别来表现一个 typeclass，我们可以用 `data` 关键字来把一个型别包在另一个里面，然后再将他定义成第二种表现方式。

而在 Haskell 中 `newtype` 正是为了这种情形，我们想将一个型别包在另一个型别中。在实际的函式库中 `ZipList a` 是这样定义了：

```haskell
newtype ZipList a = ZipList { getZipList :: [a] }
```

这边我们不用 `data` 关键字反而是用 `newtype` 关键字。这是为什么呢？第一个理由是 `newtype` 比较快速。如果你用 `data` 关键字来包一个型别的话，在你执行的时候会有一些包起来跟解开来的成本。但如果你用 `newtype` 的话，Haskell 会知道你只是要将一个现有的型别包成一个新的型别，你想要内部运作完全一样但只是要一个全新的型别而已。有了这个概念，Haskell 可以将包裹跟解开来的成本都去除掉。

那为什么我们不是一直使用 `newtype` 呢？当你用 `newtype` 来制作一个新的型别时，你只能定义单一一个值构造子，而且那个构造子只能有一个字段。但使用 `data` 的话，你可以让那个型别有好几个值构造子，并且每个构造子可以有零个或多个字段。

```haskell
data Profession = Fighter | Archer | Accountant  

data Race = Human | Elf | Orc | Goblin  

data PlayerCharacter = PlayerCharacter Race Profession
```

当使用 `newtype`的时候，你是被限制只能用一个值构造子跟单一字段。

对于 `newtype` 我们也能使用 `deriving` 关键字。我们可以 derive 像是 `Eq`, `Ord`, `Enum`, `Bounded`, `Show` 跟 `Read` 的 instance。如果我们想要对新的型别做 derive，那原本的型别必须已经在那个 typeclass 中。这样很合理，毕竟 `newtype` 就是要将现有的型别包起来。如果我们按照下面的方式定义的话，我们就能对我们的型别做印出以及比较相等性的操作：

```haskell
newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show)
```

我们来跑跑看：

```haskell
ghci> CharList "this will be shown!"  
CharList {getCharList = "this will be shown!"}  
ghci> CharList "benny" == CharList "benny"  
True  
ghci> CharList "benny" == CharList "oisters"  
False
```

对于这个 `newtype`，他的值构造子有下列型别：

```haskell
CharList :: [Char] -> CharList
```

他接受一个 `[Char]` 的值，例如 `"my sharona"` 并回传一个 `CharList` 的值。从上面我们使用 `CharList` 的值构造子的范例中，我们可以看到的确是这样。相反地，`getCharList` 具有下列的型别。

```haskell
getCharList :: CharList -> [Char]
```

他接受一个 `CharList` 的值并将他转成 `[Char]`。你可以将这个想成包装跟解开的动作，但你也可以将他想成从一个型别转成另一个型别。

### Using newtype to make type class instances

有好几次我们想要让我们的型别属于某个 typeclass，但型别变量并没有符合我们想要的。要把 `Maybe` 定义成 `Functor` 的 instance 很容易，因为 `Functor` 这个 typeclass 被定义如下：

```haskell
class Functor f where  
    fmap :: (a -> b) -> f a -> f b
```

我们先定义如下：

```haskell
instance Functor Maybe where
```

然后我们实作 `fmap`。当所有的型别变量被填上时，由于 `Maybe` 取代了 `Functor` 中 `f` 的位置，所以如果我们看看 `fmap` 运作在 `Maybe` 上时是什么样，他会像这样：

```haskell
fmap :: (a -> b) -> Maybe a -> Maybe b
```

![](/files/-MlVFmpTmti9g0Sg6Ipa)

看起来不错吧？现在我们想要 tuple 成为 `Functor` 的一个 instance，所以当我们用 `fmap` 来 map over 一个 tuple 时，他会先套用到 tuple 中的第一个元素。这样当我们做 `fmap (+3) (1,1)` 会得到 `(4,1)`。不过要定义出这样的 instance 有些困难。对于 `Maybe`，我们只要写 `instance Functor Maybe where`，这是因为对于只吃一个参数的型别构造子我们很容易定义成 `Functor` 的 instance。但对于 `(a,b)` 这样的就没办法。要绕过这样的困境，我们可以用 `newtype` 来重新定义我们的 tuple，这样第二个型别参数就代表了 tuple 中的第一个元素部份。

```haskell
newtype Pair b a = Pair { getPair :: (a,b) }
```

现在我们可以将他定义成 `Functor` 的 instance，所以函数被 map over tuple 中的第一个部份。

```haskell
instance Functor (Pair c) where  
    fmap f (Pair (x,y)) = Pair (f x, y)
```

正如你看到的，我们可以对 newtype 定义的型别做模式匹配。我们用模式匹配来拿到底层的 tuple，然后我们将 `f` 来套用至 tuple 的第一个部份，然后我们用 `Pair` 这个值构造子来将 tuple 转换成 `Pair b a`。如果我们问 `fmap` 的型别究竟是什么，他会是：

```haskell
fmap :: (a -> b) -> Pair c a -> Pair c b
```

我们说过 `instance Functor (Pair c) where` 跟 `Pair c` 取代了 `Functor` 中 `f` 的位置：

```haskell
class Functor f where  
    fmap :: (a -> b) -> f a -> f b
```

如果我们将一个 tuple 转换成 `Pair b a`，我们可以用 `fmap` 来 map over 第一个部份。

```haskell
ghci> getPair $ fmap (*100) (Pair (2,3))  
(200,3)  
ghci> getPair $ fmap reverse (Pair ("london calling", 3))  
("gnillac nodnol",3)
```

### On newtype laziness

我们提到 `newtype` 一般来讲比 `data` 来得有效率。`newtype` 能做的唯一一件事就是将现有的型别包成新的型别。这样 Haskell 在内部就能将新的型别的值用旧的方式来操作。只是要记住他们还是不同的型别。这代表 `newtype` 并不只是有效率，他也具备 lazy 的特性。我们来说明一下这是什么意思。

就像我们之前说得，Haskell 缺省是具备 lazy 的特性，这代表只有当我们要将函数的结果印出来的时候计算才会发生。或者说，只有当我们真的需要结果的时候计算才会发生。在 Haskell 中 `undefined` 代表会造成错误的计算。如果我们试着计算他，也就是将他印到终端中，Haskell 会丢出错误。

```haskell
ghci> undefined  
*** Exception: Prelude.undefined
```

然而，如果我们做一个 list，其中包含一些 `undefined` 的值，但却要求一个不是 `undefined` 的 head，那一切都会顺利地被计算，因为 Haskell 并不需要 list 中其他元素来得到结果。我们仅仅需要看到第一个元素而已。

```haskell
ghci> head [3,4,5,undefined,2,undefined]  
3
```

现在们考虑下面的型别：

```haskell
data CoolBool = CoolBool { getCoolBool :: Bool }
```

这是一个用 `data` 关键字定义的 algebraic data type。他有一个值建构子并只有一个型别为 `Bool` 的字段。我们写一个函数来对 `CoolBool` 做模式匹配，并回传一个 `"hello"` 的值。他并不会管 `CoolBool` 中装的究竟是 `True` 或 `False`。

```haskell
helloMe :: CoolBool -> String  
helloMe (CoolBool _) = "hello"
```

这次我们不喂给这个函数一个普通的 `CoolBool`，而是丢给他一个 `undefined`。

```
ghci> helloMe undefined  
"*** Exception: Prelude.undefined  "
```

结果收到了一个 Exception。是什么造成这个 Exception 的呢？用 `data` 定义的型别可以有好几个值构造子（尽管 `CoolBool` 只有一个）所以当我们要看看喂给函数的值是否是 `(CoolBool _)` 的形式，Haskell 会需要做一些基本的计算来看看是哪个值构造子被用到。但当我们计算 `undefined` 的时候，就算是一点也会丢出 Exception。

我们不用 `data` 来定义 `CoolBool` 而用 `newtype`：

```haskell
newtype CoolBool = CoolBool { getCoolBool :: Bool }
```

我们不用修改 `helloMe` 函数，因为对于模式匹配使用 `newtype` 或 `data` 都是一样。我们再来将 `undefined` 喂给 `helloMe`。

```haskell
ghci> helloMe undefined  
"hello"
```

居然正常运作！为什么呢？正如我们说过得，当我们使用 `newtype` 的时候，Haskell 内部可以将新的型别用旧的型别来表示。他不必加入另一层 box 来包住旧有的型别。他只要注意他是不同的型别就好了。而且 Haskell 会知道 `newtype` 定义的型别一定只会有一个构造子，他不必计算喂给函数的值就能确定他是 `(CoolBool _)` 的形式，因为 `newtype` 只有一个可能的值跟单一字段！

这样行为的差异可能没什么关系，但实际上他非常重要。因为他让我们认知到尽管从撰写程序的观点来看没什么差异，但他们的确是两种不同的机制。尽管 `data` 可以让你从无到有定义型别，`newtype` 是从一个现有的型别做出来的。对 `newtype` 做模式匹配并不是像从盒子中取出东西，他比较像是将一个型别转换成另一个型别。

### type vs newtype vs data

到目前为止，你也许对于 `type`,`data` 跟 `newtype` 之间的差异还不是很了解，让我们快速复习一遍。

`type` 关键字是让我们定义 type synonyms。他代表我们只是要给一个现有的型别另一个名字，假设我们这样做：

```haskell
type IntList = [Int]
```

这样做可以允许我们用 `IntList` 的名称来指称 `[Int]`。我们可以交换地使用他们。但我们并不会因此有一个 `IntList` 的值构造子。因为 `[Int]` 跟 `IntList` 只是两种指称同一个型别的方式。我们在指称的时候用哪一个并无所谓。

```haskell
ghci> ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int])  
[1,2,3,1,2,3]
```

当我们想要让 type signature 更清楚一些，给予我们更了解函数的 context 的时候，我们会定义 type synonyms。举例来说，当我们用一个型别为 `[(String,String)]` 的 association list 来代表一个电话簿的时候，我们可以定义一个 `PhoneBook` 的 type synonym，这样 type signature 会比较容易读。

`newtype` 关键字将现有的型别包成一个新的型别，大部分是为了要让他们可以是特定 typeclass 的 instance 而这样做。当我们使用 `newtype` 来包裹一个现有的型别时，这个型别跟原有的型别是分开的。如果我们将下面的型别用 `newtype` 定义：

```haskell
newtype CharList = CharList { getCharList :: [Char] }
```

我们不能用 `++` 来将 `CharList` 跟 `[Char]` 接在一起。我们也不能用 `++` 来将两个 `CharList` 接在一起，因为 `++` 只能套用在 list 上，而 `CharList` 并不是 list，尽管你会说他包含一个 list。但我们可以将两个 `CharList` 转成 list，将他们 `++` 然后再转回 `CharList`。

当我们在 `newtype` 宣告中使用 record syntax 的时候，我们会得到将新的型别转成旧的型别的函数，也就是我们 `newtype` 的值构造子，以及一个函数将他的字段取出。新的型别并不会被自动定义成原有型别所属的 typeclass 的一个 instance，所以我们必须自己来 derive 他们。

实际上你可以将 `newtype` 想成是只能定义一个构造子跟一个字段的 `data` 宣告。如果你碰到这种情形，可以考虑使用 `newtype`。

使用 `data` 关键字是为了定义自己的型别。他们可以在 algebraic data type 中放任意数量的构造子跟字段。可以定义的东西从 list, `Maybe` 到 tree。

如果你只是希望你的 type signature 看起来比较干净，你可以只需要 type synonym。如果你想要将现有的型别包起来并定义成一个 type class 的 instance，你可以尝试使用 newtype。如果你想要定义完全新的型别，那你应该使用 `data` 关键字。

## Monoids

Haskell 中 typeclass 是用来表示一个型别之间共有的行为，是一种 interface。我们介绍过 `Eq`，他定义型别是否可以比较相等性，以及 `Ord`，他表示可以被排序的型别。还介绍了更有趣的像是 `Functor` 跟 `Applicative`。

当我们定义一个型别时，我们会想说他应该要支持的行为。也就是表现的行为是什么，并且要让他属于哪些 typeclass。如果希望他可以比较相等与否，那我们就应该定义他成为 `Eq` 的一个 instance。如果我们想要看看型别是否是一种 functor，我们可以定义他是 `Functor` 的一个 instance。以此类推。

考虑 `*` 是一个将两个数值相乘的一个函数。如果我们将一个数值乘上 `1`，那就会得到自身的数值。我们实际上是做 `1 * x` 或 `x * 1` 并没有差别。结果永远会是 `x`。同样的，`++` 是一个接受两个参数并回传新的值的一个函数。只是他不是相乘而是将两个 list 接在一起。而类似 `*`，他也有一个特定的值，当他跟其他值使用 `++` 时会得到同样的值。那个值就是空的 list `[]`。

```haskell
ghci> 4 * 1  
4  
ghci> 1 * 9  
9  
ghci> [1,2,3] ++ []  
[1,2,3]  
ghci> [] ++ [0.5, 2.5]  
[0.5,2.5]
```

看起来 `*` 之于 `1` 跟 `++` 之于 `[]` 有类似的性质：

```
# 函数同样接受两个参数
# 参数跟回传值是同样的型别
# 同样存在某些值当套用二元函数时并不会改变其他值
```

关于这两种操作还有另一个比较难察觉的性质就是，当我们对这个二元函数对三个以上的值操作并化简，函数套用的顺序并不会影响到结果。不论是 `(3 * 4) * 5` 或是 `3 * (4 * 5)`，两种方式都会得到 `60`。而 `++` 也是相同的。

```haskell
ghci> (3 * 2) * (8 * 5)  
240  
ghci> 3 * (2 * (8 * 5))  
240  
ghci> "la" ++ ("di" ++ "da")  
"ladida"  
ghci> ("la" ++ "di") ++ "da"  
"ladida"
```

我们称呼这样的性质为结合律(associativity)。`*` 遵守结合律，`++` 也是。但 `-` 就不遵守。`(5 - 3) - 4` 跟 `5 - (3 - 4)` 得到的结果是不同的。

注意到这些性质并具体地写下来，就可以得到 monoid。一个 monoid 是你有一个遵守结合律的二元函数还有一个可以相对于那个函数作为 identity 的值。当某个值相对于一个函数是一个 identity，他表示当我们将这个值丢给函数时，结果永远会是另外一边的那个值本身。`1` 是相对于 `*` 的 identity，而 `[]` 是相对于 `++` 的 identity。在 Haskell 中还有许多其他的 monoid，这也是为什么我们定义了 `Monoid` 这个 typeclass。他描述了表现成 monoid 的那些型别。我们来看看这个 typeclass 是怎么被定义的：

```haskell
class Monoid m where  
    mempty :: m  
    mappend :: m -> m -> m  
    mconcat :: [m] -> m  
    mconcat = foldr mappend mempty
```

![](/files/-MlVFmpXiXQxF7Rf7LgL)

`Monoid` typeclass 被定义在 `import Data.Monoid` 中。我们来花些时间好好了解他。

首先我们看到只有具体型别才能定义成 `Monoid` 的 instance。由于在 typeclass 定义中的 `m` 并不接受任何型别参数。这跟 `Functor` 以及 `Applicative` 不同，他们要求他们的 instance 必须是一个接受单一型别参数的型别构造子。

第一个函数是 `mempty`，由于他不接受任何参数，所以他并不是一个函数，而是一个 polymorphic 的常数。有点像是 `Bounded` 中的 `minBound` 一样。`mempty` 表示一个特定 monoid 的 identity。

再来我们看到 `mappend`，你可能已经猜到，他是一个接受两个相同型别的值的二元函数，并回传同样的型别。不过要注意的是他的名字不太符合他真正的意思，他的名字隐含了我们要将两个东西接在一起。尽管在 list 的情况下 `++` 的确将两个 list 接起来，但 `*` 则否。他只不过将两个数值做相乘。当我们再看到其他 `Monoid` 的 instance 时，我们会看到他们大部分都没有接起来的做，所以不要用接起来的概念来想像 `mappend`，只要想像他们是接受两个 monoid 的值并回传另外一个就好了。

在 typeclass 定义中的最后一个函数是 `mconcat`。他接受一串 monoid 值，并将他们用 `mappend` 简化成单一的值。他有一个缺省的实作，就是从 `mempty` 作为起始值，然后用 `mappend` 来 fold。由于对于大部分的 instance 缺省的实作就没什么问题，我们不会想要实作自己的 `mconcat`。当我们定义一个型别属于 `Monoid` 的时候，多半实作 `mempty` 跟 `mappend` 就可以了。而 `mconcat` 就是因为对于一些 instance，有可能有比较有效率的方式来实作 `mconcat`。不过大多数情况都不需要。

在我们继续接下去看几个 `Monoid` 的例子前，我们来看一下 monoid law。我们提过必须有一个值作为 identity 以及一个遵守结合律的二元函数当作前提。我们是可以定义一个 `Monoid` 的 instance 却不遵守这些定律的，但这样写出来的 instance 就没有用了，因为我们在使用 `Monoid` 的时候都是依靠这些定律才可以称作实质上的 monoid。所以我们必须确保他们遵守：

```
# ``mempty `mappend` x = x``
# ``x `mappend` mempty = x``
# ``(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)``
```

前两个描述了 `mempty` 相对于 `mappend` 必须要表现成 identity。而第三个定律说了 `mappend` 必须要遵守结合律。也就是说我们做 `mappend` 顺序并不重要。Haskell 不会自己检查这些定律是否有被遵守。所以你必须自己小心地检查他们。

### Lists are monoids

没错，list 是一种 monoid。正如我们先前看到的，`++` 跟空的 list `[]` 共同形成了一个 monoid。他的 instance 很简单：

```haskell
instance Monoid [a] where  
    mempty = []  
    mappend = (++)
```

list 是 `Monoid` typeclass 的一个 instance，这跟他们装的元素的型别无关。注意到我们写 `instance Monoid [a]` 而非 `instance Monoid []`，这是因为 `Monoid` 要求 instance 必须是具体型别。

我们试着跑跑看，得到我们预期中的结果：

```haskell
ghci> [1,2,3] `mappend` [4,5,6]  
[1,2,3,4,5,6]  
ghci> ("one" `mappend` "two") `mappend` "tree"  
"onetwotree"  
ghci> "one" `mappend` ("two" `mappend` "tree")  
"onetwotree"  
ghci> "one" `mappend` "two" `mappend` "tree"  
"onetwotree"  
ghci> "pang" `mappend` mempty  
"pang"  
ghci> mconcat [[1,2],[3,6],[9]]  
[1,2,3,6,9]  
ghci> mempty :: [a]  
[]
```

![](/files/-MlVFmpZgmstDIkjxqKx)

注意到最后一行我们明白地标记出型别。这是因为如果只些 `mempty` 的话，GHCi 不会知道他是哪一个 instance 的 `mempty`，所以我们必须清楚说出他是 list instance 的 mempty。我们可以使用一般化的型别 `[a]`，因为空的 list 可以看作是属于任何型别。

由于 `mconcat` 有一个缺省的实作，我们将某个型别定义成 `Monoid` 的型别时就可以自动地得到缺省的实作。但对于 list 而言，`mconcat` 其实就是 `concat`。他接受一个装有 list 的 list，并把他用 `++` 来扁平化他。

list 的 instance 也遵守 monoid law。当我们有好几个 list 并且用 `mappend` 来把他们串起来，先后顺序并不是很重要，因为他们都是接在最后面。而且空的 list 也表现得如 identity 一样。注意到 monoid 并不要求 ``a `mappend` b`` 等于 ``b `mappend` a``。在 list 的情况下，他们明显不相等。

```haskell
ghci> "one" `mappend` "two"  
"onetwo"  
ghci> "two" `mappend` "one"  
"twoone"
```

这样并没有关系。`3 * 5` 跟 `5 * 3` 会相等只不过是乘法的性质而已，但没有保证所有 monoid 都要遵守。

### Product and Sum

我们已经描述过将数值表现成一种 monoid 的方式。只要将 `*` 当作二元函数而 `1` 当作 identity 就好了。而且这不是唯一一种方式，另一种方式是将 `+` 作为二元函数而 `0` 作为 identity。

```haskell
ghci> 0 + 4  
4  
ghci> 5 + 0  
5  
ghci> (1 + 3) + 5  
9  
ghci> 1 + (3 + 5)  
9
```

他也遵守 monoid law，因为将 0 加上其他数值，都会是另外一者。而且加法也遵守结合律。所以现在我们有两种方式来将数值表现成 monoid，那要选哪一个呢？其实我们不必要强迫定下来，还记得当同一种型别有好几种表现成某个 typeclass 的方式时，我们可以用 `newtype` 来包裹现有的型别，然后再定义新的 instance。这样就行了。

`Data.Monoid` 这个模块导出了两种型别，`Product` 跟 `Sum`。`Product` 定义如下：

```haskell
newtype Product a =  Product { getProduct :: a }  
    deriving (Eq, Ord, Read, Show, Bounded)
```

简单易懂，就是一个单一型别参数的 `newtype`，并 derive 一些性质。他的 `Monoid` 的 instance 长得像这样：

```haskell
instance Num a => Monoid (Product a) where  
    mempty = Product 1  
    Product x `mappend` Product y = Product (x * y)
```

`mempty` 只不过是将 `1` 包在 `Product` 中。`mappend` 则对 `Product` 的构造子做模式匹配，将两个取出的数值相乘后再将结果放回去。就如你看到的，typeclass 定义前面有 `Num a` 的条件限制。所以他代表 `Product a` 对于所有属于 `Num` 的 `a` 是一个 `Monoid`。要将 `Product a` 作为一个 monoid 使用，我们需要用 newtype 来做包裹跟解开的动作。

```haskell
ghci> getProduct $ Product 3 `mappend` Product 9  
27  
ghci> getProduct $ Product 3 `mappend` mempty  
3  
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2  
24  
ghci> getProduct . mconcat . map Product $ [3,4,2]  
24
```

这当作 `Monoid` 的一个演练还不错，但并不会有人觉得这会比 `3 * 9` 跟 `3 * 1` 这种方式来做乘法要好。但我们稍后会说明尽管像这种显而易见的定义还是有他方便的地方。

`Sum` 跟 `Product` 定义的方式类似，我们也可以用类似的方式操作：

```haskell
ghci> getSum $ Sum 2 `mappend` Sum 9  
11  
ghci> getSum $ mempty `mappend` Sum 3  
3  
ghci> getSum . mconcat . map Sum $ [1,2,3]  
6
```

### Any and ALL

另一种可以有两种表示成 monoid 方式的型别是 `Bool`。第一种方式是将 `||` 当作二元函数，而 `False` 作为 identity。这样的意思是只要有任何一个参数是 `True` 他就回传 `True`，否则回传 `False`。所以如果我们使用 `False` 作为 identity，他会在跟 `False` 做 OR 时回传 `False`，跟 `True` 做 OR 时回传 `True`。`Any` 这个 newtype 是 `Monoid` 的一个 instance，并定义如下：

```haskell
newtype Any = Any { getAny :: Bool }  
    deriving (Eq, Ord, Read, Show, Bounded)
```

他的 instance 长得像这样：

```haskell
instance Monoid Any where  
    mempty = Any False  
    Any x `mappend` Any y = Any (x || y)
```

他叫做 `Any` 的理由是 ``x `mappend` y`` 当有任何一个是 `True` 时就会是 `True`。就算是更多个用 `mappend` 串起来的 `Any`，他也会在任何一个是 `True` 回传 `True`。

```haskell
ghci> getAny $ Any True `mappend` Any False  
True  
ghci> getAny $ mempty `mappend` Any True  
True  
ghci> getAny . mconcat . map Any $ [False, False, False, True]  
True  
ghci> getAny $ mempty `mappend` mempty  
False
```

另一种 `Bool` 表现成 `Monoid` 的方式是用 `&&` 作为二元函数，而 `True` 作为 identity。只有当所有都是 `True` 的时候才会回传 `True`。下面是他的 newtype 定义：

```haskell
newtype All = All { getAll :: Bool }  
        deriving (Eq, Ord, Read, Show, Bounded)
```

而这是他的 instance：

```haskell
instance Monoid All where  
        mempty = All True  
        All x `mappend` All y = All (x && y)
```

当我们用 `mappend` 来串起 `All` 型别的值时，结果只有当所有 `mappend` 的值是 `True` 时才会是 `True`：

```haskell
ghci> getAll $ mempty `mappend` All True  
True  
ghci> getAll $ mempty `mappend` All False  
False  
ghci> getAll . mconcat . map All $ [True, True, True]  
True  
ghci> getAll . mconcat . map All $ [True, True, False]  
False
```

就如乘法跟加法一样，我们通常宁愿用二元函数来操作他们也不会用 newtype 来将他们包起来。不会将他们包成 `Any` 或 `All` 然后用 `mappend`，`mempty` 或 `mconcat` 来操作。通常使用 `or` 跟 `and`，他们接受一串 `Bool`，并只有当任意一个或是所有都是 `True` 的时候才回传 `True`。

### The Ordering monoid

还记得 `Ordering` 型别吗?他是比较运算之后得到的结果，包含三个值：`LT`，`EQ` 跟 `GT`，分别代表小于，等于跟大于：

```haskell
ghci> 1 `compare` 2  
LT  
ghci> 2 `compare` 2  
EQ  
ghci> 3 `compare` 2  
GT
```

针对 list，数值跟布尔值而言，要找出 monoid 的行为只要去查看已经定义的函数，然后看看有没有展现出 monoid 的特性就可以了，但对于 `Ordering`，我们就必须要更仔细一点才能看出来是否是一个 monoid，但其实他的 `Monoid` instance 还蛮直觉的：

```haskell
instance Monoid Ordering where  
    mempty = EQ  
    LT `mappend` _ = LT  
    EQ `mappend` y = y  
    GT `mappend` _ = GT
```

![](/files/-MlVFmpdViXRh3yucFTs)

这个 instance 定义如下：当我们用 `mappend` 两个 `Ordering` 型别的值时，左边的会被保留下来。除非左边的值是 `EQ`，那我们就会保留右边的当作结果。而 identity 就是 `EQ`。乍看之下有点随便，但实际上他是我们比较两个英文本时所用的方法。我们先比较两个字母是否相等，如果他们不一样，那我们就知道那一个字在字典中会在前面。而如果两个字母相等，那我们就继续比较下一个字母，以此类推。

举例来说，如果我们字典顺序地比较 `"ox"` 跟 `"on"` 的话。我们会先比较两个字的首个字母，看看他们是否相等，然后继续比较第二个字母。我们看到 `'x'` 是比 `'n'` 要来得大，所以我们就知道如何比较两个字了。而要了解为何 `EQ` 是 identity，我们可以注意到如果我们在两个字中间的同样位置塞入同样的字母，那他们之间的字典顺序并不会改变。`"oix"` 仍然比 `"oin"` 要大。

很重要的一件事是在 `Ordering` 的 `Monoid` 定义里 ``x `mappend` y`` 并不等于 ``y `mappend` x``。因为除非第一个参数是 `EQ`，不然结果就会是第一个参数。所以 ``LT `mappend` GT`` 等于 `LT`，然而 ``GT `mappend` LT`` 等于 `GT`。

```haskell
ghci> LT `mappend` GT  
LT  
ghci> GT `mappend` LT  
GT  
ghci> mempty `mappend` LT  
LT  
ghci> mempty `mappend` GT  
GT
```

所以这个 monoid 在什么情况下会有用呢？假设你要写一个比较两个字串长度的函数，并回传 `Ordering`。而且当字串一样长的时候，我们不直接回传 `EQ`，反而继续用字典顺序比较他们。一种实作的方式如下：

```haskell
lengthCompare :: String -> String -> Ordering  
lengthCompare x y = let a = length x `compare` length y   
                        b = x `compare` y  
                    in  if a == EQ then b else a
```

我们称呼比较长度的结果为 `a`，而比较字典顺序的结果为 `b`，而当长度一样时，我们就回传字典顺序。

如果善用我们 `Ordering` 是一种 monoid 这项知识，我们可以把我们的函数写得更简单些：

```haskell
import Data.Monoid

lengthCompare :: String -> String -> Ordering  
lengthCompare x y = (length x `compare` length y) `mappend`  
                    (x `compare` y)
```

我们可以试着跑跑看：

```haskell
ghci> lengthCompare "zen" "ants"  
LT  
ghci> lengthCompare "zen" "ant"  
GT
```

要记住当我们使用 `mappend`。他在左边不等于 `EQ` 的情况下都会回传左边的值。相反地则回传右边的值。这也是为什么我们将我们认为比较重要的顺序放在左边的参数。如果我们要继续延展这个函数，要让他们比较元音的顺序，并把这顺串行为第二重要，那我们可以这样修改他：

```haskell
import Data.Monoid  

lengthCompare :: String -> String -> Ordering  
lengthCompare x y = (length x `compare` length y) `mappend`  
                    (vowels x `compare` vowels y) `mappend`  
                    (x `compare` y)  
    where vowels = length . filter (`elem` "aeiou")
```

我们写了一个辅助函数，他接受一个字串并回传他有多少元音。他是先用 filter 来把字母滤到剩下 `"aeiou"`，然后再用 `length` 计算长度。

```haskell
ghci> lengthCompare "zen" "anna"  
LT  
ghci> lengthCompare "zen" "ana"  
LT  
ghci> lengthCompare "zen" "ann"  
GT
```

在第一个例子中我们看到长度不同所以回传 `LT`，明显地 `"zen"` 要短于 `"anna"`。在第二个例子中，长度是一样的，但第二个字串有比较多的元音，所以结果仍然是 `LT`。在第三个范例中，两个长度都相等，他们也有相同个数的元音，经由字典顺序比较后得到 `"zen"` 比较大。

`Ordering` 的 monoid 允许我们用不同方式比较事物，并将这些顺序也定义了依重要度不同的一个顺序。

### Maybe the monoid

我们来看一下 `Maybe a` 是怎样有多种方式来表现成 `Monoid` 的，并且说明哪些是比较有用的。一种将 `Maybe a` 当作 monoid 的方式就是他的 `a` 也是一个 monoid，而我们将 `mappend` 实作成使用包在 `Just` 里面的值对应的 `mappend`。并且用 `Nothing` 当作 identity。所以如果我 `mappend` 两个参数中有一个是 `Nothing`。那结果就会是另一边的值。他的 instance 定义如下：

```haskell
instance Monoid a => Monoid (Maybe a) where  
    mempty = Nothing  
    Nothing `mappend` m = m  
    m `mappend` Nothing = m  
    Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
```

留意到 class constraint。他说明 `Maybe a` 只有在 `a` 是 `Monoid` 的情况下才会是一个 `Monoid`。如果我们 `mappend` 某个东西跟 `Nothing`。那结果就会是某个东西。如果我们 `mappend` 两个 `Just`，那 `Just` 包住的结果就会 `mappended` 在一起并放回 `Just`。我们能这么做是因为 class constraint 保证了在 `Just` 中的值是 `Monoid`。

```haskell
ghci> Nothing `mappend` Just "andy"  
Just "andy"  
ghci> Just LT `mappend` Nothing  
Just LT  
ghci> Just (Sum 3) `mappend` Just (Sum 4)  
Just (Sum {getSum = 7})
```

这当你在处理有可能失败的 monoid 的时候比较有用。有了这个 instance，我们就不必一一去检查他们是否失败，是否是 `Nothing` 或是 `Just`，我们可以直接将他们当作普通的 monoid。

但如果在 `Maybe` 中的型别不是 `Monoid` 呢？注意到在先前的 instance 定义中，唯一有依赖于 monoid 限制的情况就是在 `mappend` 两个 `Just` 的时候。但如果我们不知道包在 `Just` 里面的值究竟是不是 monoid，我们根本无法用 `mappend` 操作他们，所以该怎么办呢？一种方式就是直接丢掉第二个值而留下第一个值。这就是 `First a` 存在的目的，而这是他的定义：

```haskell
newtype First a = First { getFirst :: Maybe a }  
    deriving (Eq, Ord, Read, Show)
```

我们接受一个 `Maybe a` 并把他包成 newtype，`Monoid` 的定义如下：

```haskell
instance Monoid (First a) where  
    mempty = First Nothing  
    First (Just x) `mappend` _ = First (Just x)  
    First Nothing `mappend` x = x
```

正如我们说过得，`mempty` 就是包在 `First` 中的 `Nothing`。如果 `mappend` 的第一个参数是 `Just`，我们就直接忽略第二个参数。如果第一个参数是 `Nothing`，那我们就将第二个参数当作结果。并不管他究竟是 `Just` 或是 `Nothing`：

```haskell
ghci> getFirst $ First (Just 'a') `mappend` First (Just 'b')  
Just 'a'  
ghci> getFirst $ First Nothing `mappend` First (Just 'b')  
Just 'b'  
ghci> getFirst $ First (Just 'a') `mappend` First Nothing  
Just 'a'
```

`First` 在我们有一大串 `Maybe` 而且想知道他们之中就竟有没有 `Just` 的时候很有用。可以利用 `mconcat`：

```haskell
ghci> getFirst . mconcat . map First $ [Nothing, Just 9, Just 10]  
Just 9
```

如果我们希望定义一个 `Maybe a` 的 monoid，让他当 `mappend` 的两个参数都是 `Just` 的时候将第二个参数当作结果。`Data.Monoid` 中有一个现成的 `Last a`，他很像是 `First a`，只差在 `mappend` 跟 `mconcat` 会保留最后一个非 `Nothing` 的值。

```haskell
ghci> getLast . mconcat . map Last $ [Nothing, Just 9, Just 10]  
Just 10  
ghci> getLast $ Last (Just "one") `mappend` Last (Just "two")  
Just "two"
```

### Using monoids to fold data structures

另一种有趣的 monoid 使用方式就是让他来帮助我们 fold 一些数据结构。到目前为止我们只有 fold list。但 list 并不是唯一一种可以 fold 的数据结构。我们几乎可以 fold 任何一种数据结构。像是 tree 也是一种常见的可以 fold 的数据结构。

由于有太多种数据结构可以 fold 了，所以我们定义了 `Foldable` 这个 typeclass。就像 `Functor` 是定义可以 map over 的结构。`Foldable` 是定义可以 fold 的结构。在 `Data.Foldable` 中有定义了一些有用的函数，但他们名称跟 `Prelude` 中的名称冲突。所以最好是用 qualified 的方式 import 他们：

```haskell
import qualified Foldable as F
```

为了少打一些字，我们将他们 import qualified 成 `F`。所以这个 typeclass 中定义了哪些函数呢？有 `foldr`，`foldl`，`foldr1` 跟 `foldl1`。你会说我们已经知道这些函数了，他们有什么不一样的地方吗？我们来比较一下 `Foldable` 中的 `foldr` 跟 `Prelude` 中的 `foldr` 的型别异同：

```haskell
ghci> :t foldr  
foldr :: (a -> b -> b) -> b -> [a] -> b  
ghci> :t F.foldr  
F.foldr :: (F.Foldable t) => (a -> b -> b) -> b -> t a -> b
```

尽管 `foldr` 接受一个 list 并将他 fold 起来，`Data.Foldable` 中的 `foldr` 接受任何可以 fold 的型别。并不只是 list。 而两个 `foldr` 对于 list 的结果是相同的：

```haskell
ghci> foldr (*) 1 [1,2,3]  
6  
ghci> F.foldr (*) 1 [1,2,3]  
6
```

那有哪些数据结构支持 fold 呢？首先我们有 `Maybe`：

```haskell
ghci> F.foldl (+) 2 (Just 9)  
11  
ghci> F.foldr (||) False (Just True)  
True
```

但 fold 一个 `Maybe` 并没什么新意。毕竟当他是 `Just` 的时候表现得像是只有单一元素的 list，而当他是 `Nothing` 的时候就像是空的 list 一样。所以我们来看一些比较复杂的数据结构。

还记得 Making Our Own Types and Typeclass 章节中的树状的数据结构吗？我们是这样定义的：

```haskell
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
```

我们说一棵树要不就是一棵空的树要不然就是一个包含值的节点，并且还指向另外两棵树。定义他之后，我们将他定义成 `Functor` 的 instance，因此可以 `fmap` 他。现在我们要将他定义成 `Foldable` 的 instance，这样我们就可以 fold 他。要定义成 `Foldable` 的一种方式就是实作 `foldr`。但另一种比较简单的方式就是实作 `foldMap`，他也属于 `Foldable` typeclass。`foldMap` 的型别如下：

```haskell
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
```

第一个参数是一个函数，这个函数接受 foldable 数据结构中包含的元素的型别，并回传一个 monoid。他第二个参数是一个 foldable 的结构，并包含型别 `a` 的元素。他将第一个函数来 map over 这个 foldable 的结构，因此得到一个包含 monoid 的 foldable 结构。然后用 `mappend` 来简化这些 monoid，最后得到单一的一个 monoid。这个函数听起来不太容易理解，但我们下面会看到他其实很容易实作。而且好消息是只要实作了这个函数就可以让我们的函数成为 `Foldable`。所以我们只要实作某个型别的 `foldMap`，我们就可以得到那个型别的 `foldr` 跟 `foldl`。

这就是我们如何定义 `Tree` 成为 `Foldable` 的：

```haskell
instance F.Foldable Tree where  
    foldMap f Empty = mempty  
    foldMap f (Node x l r) = F.foldMap f l `mappend`  
                                f x           `mappend`  
                                F.foldMap f r
```

![](/files/-MlVFmpiSb6eLfxCzSeg)

我们是这样思考的：如果我们写一个函数，他接受树中的一个元素并回传一个 monoid，那我们要怎么简化整棵树到只有单一一个 monoid？当我们在对树做 `fmap` 的时候，我们将那函数套用至节点上，并递归地套用至左子树以及右子树。这边我们不只是 map 一个函数而已，我们还要求要把结果用 `mappend` 简化成只有单一一个 monoid 值。首先我们考虑树为空的情形，一棵没有值也没有子树的情形。由于没有值我们也没办法将他套用上面转换成 monoid 的函数，所以当树为空的时候，结果应该要是 `mempty`。

在非空节点的情形下比较有趣，他包含一个值跟两棵子树。在这种情况下，我们递归地做 `foldMap`，用 `f` 来套用到左子树跟右子树上。要记住我们的 `foldMap` 只会得到单一的 monoid 值。我们也会套用 `f` 到节点中的值。这样我们就得到三个 monoid 值，有两个来自简化子树的结果，还有一个是套用 `f` 到节点中的值的结果。而我们需要将这三个值集成成单一个值。要达成这个目的我们使用 `mappend`，而且自然地会想到照左子树，节点值以及右子树的顺序来简化。

注意到我们并不一定要提供一个将普通值转成 monoid 的函数。我们只是把他当作是 `foldMap` 的参数，我们要决定的只是如何套用那个函数，来把得到的 monoid 们简化成单一结果。

现在我们有树的 `Foldable` instance，而 `foldr` 跟 `foldl` 也有缺省的实作了。考虑下面这棵树：

```haskell
testTree = Node 5  
            (Node 3  
             (Node 1 Empty Empty)  
             (Node 6 Empty Empty)  
            )  
            (Node 9  
             (Node 8 Empty Empty)  
             (Node 10 Empty Empty)  
            )
```

他的 root 是 `5`，而他左边下来分别是 `3`，再来是 `1` 跟 `6`。而右边下来是 `9`，再来是 `8` 跟 `10`。有了 `Foldable` 的定义，我们就能像对 list 做 fold 一样对树做 fold：

```haskell
ghci> F.foldl (+) 0 testTree  
42  
ghci> F.foldl (*) 1 testTree  
64800
```

`foldMap` 不只是定义 `Foldable` 新的 instance 有用。他也对简化我们的结构至单一 monoid 值有用。举例来说，如果我们想要知道我们的树中有没有 `3`，我们可以这样做：

```haskell
ghci> getAny $ F.foldMap (\x -> Any $ x == 3) testTree  
True
```

这边 `\x -> Any $ x == 3` 是一个接受一个数值并回传一个 monoid 的函数，也就是一个包在 `Any` 中的 `Bool`。`foldMap` 将这个函数套用至树的每一个节点，并把结果用 `mappend` 简化成单一 monoid。如果我们这样做：

```haskell
ghci> getAny $ F.foldMap (\x -> Any $ x > 15) testTree  
False
```

经过套用 lambda 之后我们所有的节点都会是 `Any False`。但 `mappend` 必须要至少吃到一个 `True` 才能让最后的结果变成 `True`。这也是为什么结果会是 `False`，因为我们树中所有的值都小于等于 `15`。

我们也能将 `foldMap` 配合 `\x -> [x]` 使用来将我们的树转成 list。经过套用那个函数后，所有节点都变成包含单一元素的 list。最后用 `mappend` 将这些单一元素的 list 转成一个装有全部元素的 list：

```haskell
ghci> F.foldMap (\x -> [x]) testTree  
[1,3,6,5,8,9,10]
```

这个小技巧并不限于树而已，他可以被套用在任何 `Foldable` 上。


# ch12


# 来看看几种 Monad

当我们第一次谈到 Functor 的时候，我们了解到他是一个抽象概念，代表是一种可以被 map over 的值。然后我们再将其概念提升到 Applicative Functor，他代表一种带有 context 的型态，我们可以用函数操作他而且同时还保有他的 context。

在这一章，我们会学到 Monad，基本上他是一种加强版的 Applicative Functor，正如 Applicative Functor 是 Functor 的加强版一样。

![](/files/-MlVFmZbrGi9uKS-wYmN)

我们介绍到 Functor 是因为我们观察到有许多态态都可以被 function 给 map over，了解到这个目的，便抽象化了 `Functor` 这个 typeclass 出来。但这让我们想问：如果给定一个 `a -> b` 的函数以及 `f a` 的型态，我们要如何将函数 map over 这个型态而得到 `f b`？我们知道要如何 map over `Maybe a`，`[a]` 以及 `IO a`。我们甚至还知道如何用 `a -> b` map over `r -> a`，并且会得到 `r -> b`。要回答这个问题，我们只需要看 `fmap` 的型态就好了：

```haskell
fmap :: (Functor f) => (a -> b) -> f a -> f b
```

然后只要针对 `Functor` instance 撰写对应的实作。

之后我们又看到一些可以针对 Functor 改进的地方，例如 `a -> b` 也被包在一个 Functor value 里面呢？像是 `Just (*3)`，我们要如何 apply `Just 5` 给他？如果我们不要 apply `Just 5` 而是 `Nothing` 呢？甚至给定 `[(*2),(+4)]`，我们要如何 apply 他们到 `[1,2,3]` 呢？对于此，我们抽象出 `Applicative` typeclass，这就是我们想要问的问题：

```haskell
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
```

我们也看到我们可以将一个正常的值包在一个数据型态中。例如说我们可以拿一个 `1` 然后把他包成 `Just 1`。或是把他包成 `[1]`。也可以是一个 I/O action 会产生一个 `1`。这样包装的 function 我们叫他做 `pure`。

如我们说得，一个 applicative value 可以被看作一个有附加 context 的值。例如说，`'a'` 只是一个普通的字符，但 `Just 'a'` 是一个附加了 context 的字符。他不是 `Char` 而是 `Maybe Char`，这型态告诉我们这个值可能是一个字符，也可能什么都没有。

来看看 `Applicative` typeclass 怎样让我们用普通的 function 操作他们，同时还保有 context：

```haskell
ghci> (*) <$> Just 2 <*> Just 8  
Just 16  
ghci> (++) <$> Just "klingon" <*> Nothing  
Nothing  
ghci> (-) <$> [3,4] <*> [1,2,3]  
[2,1,0,3,2,1]
```

所以我们可以视他们为 applicative values，`Maybe a` 代表可能会失败的 computation，`[a]` 代表同时有好多结果的 computation (non-deterministic computation)，而 `IO a` 代表会有 side-effects 的 computation。

Monad 是一个从 Applicative functors 很自然的一个演进结果。对于他们我们主要考量的点是：如果你有一个具有 context 的值 `m a`，你能如何把他丢进一个只接受普通值 `a` 的函数中，并回传一个具有 context 的值？也就是说，你如何套用一个型态为 `a -> m b` 的函数至 `m a`？基本上，我们要求的函数是：

```haskell
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
```

如果我们有一个漂亮的值跟一个函数接受普通的值但回传漂亮的值，那我们要如何要把漂亮的值丢进函数中？这就是我们使用 Monad 时所要考量的事情。我们不写成 `f a` 而写成 `m a` 是因为 `m` 代表的是 `Monad`，但 monad 不过就是支持 `>>=` 操作的 applicative functors。`>>=` 我们称呼他为 bind。

当我们有一个普通值 `a` 跟一个普通函数 `a -> b`，要套用函数是一件很简单的事。但当你在处理具有 context 的值时，就需要多考虑些东西，要如何把漂亮的值喂进函数中，并如何考虑他们的行为，但你将会了解到他们其实不难。

## 动手做做看: Maybe Monad

![](https://github.com/MnO2/learnyouahaskell-zh/tree/e47e717ed1af92c4e38a596af5347e7d1434c454/zh-cn/ch12/buddha.png)

现在对于什么是 Monad 已经有了些模糊的概念， 我们来看看要如何让这概念更具体一些。

不意外地，`Maybe` 是一个 Monad， 所以让我们对于他多探讨些，看看是否能跟我们所知的 Monad 概念结合起来。

```
到这边要确定你了解什么是 Applicatives。如果你知道好几种 ``Applicative`` 的 instance 还有他们代表的意含就更好了，因为 monad 不过就是对 applicative 的概念进行一次升级。
```

一个 `Maybe a` 型态的值代表型态为 `a` 的值而且具备一个可能造成错误的 context。而 `Just "dharma"` 的值代表他不是一个 `"dharma"` 的字串就是字串不见时的 `Nothing`。如果你把字串当作计算的结果，`Nothing` 就代表计算失败了。

当我们把 `Maybe` 视作 functor，我们其实要的是一个 `fmap` 来把一个函数针对其中的元素做套用。他会对 `Just` 中的元素进行套用，要不然就是保留 `Nothing` 的状态，其代表里面根本没有元素。

```haskell
ghci> fmap (++"!") (Just "wisdom")  
Just "wisdom!"  
ghci> fmap (++"!") Nothing  
Nothing
```

或者视为一个 applicative functor，他也有类似的作用。只是 applicative 也把函数包了起来。`Maybe` 作为一个 applicative functor，我们能用 `<*>` 来套用一个存在 `Maybe` 中的函数至包在另外一个 `Maybe` 中的值。他们都必须是包在 `Just` 来代表值存在，要不然其实就是 `Nothing`。当你在想套用函数到值上面的时候，缺少了函数或是值都会造成错误，所以这样做是很合理的。

```haskell
ghci> Just (+3) <*> Just 3  
Just 6  
ghci> Nothing <*> Just "greed"  
Nothing  
ghci> Just ord <*> Nothing  
Nothing
```

当我们用 applicative 的方式套用函数至 `Maybe` 型态的值时，就跟上面描述的差不多。过程中所有值都必须是 `Just`，要不然结果一定会是 `Nothing`。

```haskell
ghci> max <$> Just 3 <*> Just 6  
Just 6  
ghci> max <$> Just 3 <*> Nothing  
Nothing
```

我们来思考一下要怎么为 `Maybe` 实作 `>>=`。正如我们之前提到的，`>>=` 接受一个 monadic value，以及一个接受普通值的函数，这函数会回传一个 monadic value。`>>=` 会帮我们套用这个函数到这个 monadic value。在函数只接受普通值的情况侠，函数是如何作到这件事的呢？要作到这件事，他必须要考虑到 monadic value 的 context。

在这个案例中，`>>=` 会接受一个 `Maybe a` 以及一个型态为 `a -> Maybe b` 的函数。他会套用函数到 `Maybe a`。要厘清他怎么作到的，首先我们注意到 `Maybe` 的 applicative functor 特性。假设我们有一个函数 `\x -> Just (x+1)`。他接受一个数字，把他加 `1` 后再包回 `Just`。

```haskell
ghci> (\x -> Just (x+1)) 1  
Just 2  
ghci> (\x -> Just (x+1)) 100  
Just 101
```

如果我们喂给函数 `1`，他会计算成 `Just 2`。如果我们喂给函数 `100`，那结果便是 `Just 101`。但假如我们喂一个 `Maybe` 的值给函数呢？如果我们把 `Maybe` 想成一个 applicative functor，那答案便很清楚。如果我们拿到一个 `Just`，就把包在 `Just` 里面的值喂给函数。如果我们拿到一个 `Nothing`，我们就说结果是 `Nothing`。

我们调用 `applyMaybe` 而不调用 `>>=`。他接受 `Maybe a` 跟一个回传 `Maybe b` 的函数，并套用函数至 `Maybe a`。

```haskell
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b  
applyMaybe Nothing f  = Nothing  
applyMaybe (Just x) f = f x
```

我们套用一个 infix 函数，这样 `Maybe` 的值可以写在左边且函数是在右边：

```haskell
ghci> Just 3 `applyMaybe` \x -> Just (x+1)  
Just 4  
ghci> Just "smile" `applyMaybe` \x -> Just (x ++ " :")""  
Just "smile :""  
ghci> Nothing `applyMaybe` \x -> Just (x+1)  
Nothing  
ghci> Nothing `applyMaybe` \x -> Just (x ++ " :")")  
Nothing
```

在上述的范例中，我们看到在套用 `applyMaybe` 的时候，函数是套用在 `Just` 里面的值。当我们试图套用到 `Nothing`，那整个结果便是 `Nothing`。假如函数回传 `Nothing` 呢？

```haskell
ghci> Just 3 `applyMaybe` \x -> if x > 2 then Just x else Nothing  
Just 3  
ghci> Just 1 `applyMaybe` \x -> if x > 2 then Just x else Nothing  
Nothing
```

这正是我们期待的结果。如果左边的 monadic value 是 `Nothing`，那整个结果就是 `Nothing`。如果右边的函数是 `Nothing`，那结果也会是 `Nothing`。这跟我们之前把 `Maybe` 当作 applicative 时，过程中有任何一个 `Nothing` 整个结果就会是 `Nothing` 一样。

对于 `Maybe` 而言，我们已经找到一个方法处理漂亮值的方式。我们作到这件事的同时，也保留了 `Maybe` 代表可能造成错误的计算的意义。

你可能会问，这样的结果有用吗？由于 applicative functors 让我们可以拿一个接受普通值的函数，并让他可以操作具有 context 的值，这样看起来 applicative functors 好像比 monad 强。但我们会看到 monad 也能作到，因为他只是 applicative functors 的升级版。他们同时也能作到 applicative functors 不能作到的事情。

稍候我们会再继续探讨 `Maybe`，但我们先来看看 monad 的 type class。

## Monad type class

正如 functors 有 `Functor` 这个 type class，而 applicative functors 有一个 `Applicative` 这个 type class，monad 也有他自己的 type class：`Monad` 他看起来像这样：

```haskell
class Monad m where  
    return :: a -> m a  

    (>>=) :: m a -> (a -> m b) -> m b  

    (>>) :: m a -> m b -> m b  
    x >> y = x >>= \_ -> y  

    fail :: String -> m a  
    fail msg = error msg
```

![](/files/-MlVFmZg1F7Av4768kC2)

我们从第一行开始看。他说 `class Monad m where`。但我们之前不是提到 monad 是 applicative functors 的加强版吗？不是应该有一个限制说一个型态必须先是一个 applicative functor 才可能是一个 monad 吗？像是 `class (Applicative m) = > Monad m where`。他的确应该要有，但当 Haskell 被创造的早期，人们没有想到 applicative functor 适合被放进语言中，所以最后没有这个限制。但的确每个 monad 都是 applicative functor，即使 `Monad` 并没有这么宣告。

在 `Monad` typeclass 中定义的第一个函数是 `return`。他其实等价于 `pure`，只是名字不同罢了。他的型态是 `(Monad m) => a -> m a`。他接受一个普通值并把他放进一个最小的 context 中。也就是说他把普通值包进一个 monad 里面。他跟 `Applicative` 里面 `pure` 函数做的事情一样，所以说其实我们已经认识了 `return`。我们已经用过 `return` 来处理一些 I/O。我们用他来做一些假的 I/O，印出一些值。对于 `Maybe` 来说他就是接受一个普通值然后包进 `Just`。

```
提醒一下：``return`` 跟其他语言中的 ``return`` 是完全不一样的。他并不是结束一个函数的执行，他只不过是把一个普通值包进一个 context 里面。
```

![](/files/-MlVFmZhi-ynilgLcADJ)

接下来定义的函数是 bind: `>>=`。他就像是函数套用一样，只差在他不接受普通值，他是接受一个 monadic value（也就是具有 context 的值）并且把他喂给一个接受普通值的函数，并回传一个 monadic value。

接下来，我们定义了 `>>`。我们不会介绍他，因为他有一个事先定义好的实作，基本上我们在实作 `Monad` typeclass 的时候都不会去理他。

最后一个函数是 `fail`。我们通常在我们程序中不会具体写出来。他是被 Haskell 用在处理语法错误的情况。我们目前不需要太在意 `fail`。

我们知道了 `Monad` typeclass 长什么样子，我们来看一下 `Maybe` 的 `Monad` instance。

```haskell
instance Monad Maybe where  
    return x = Just x  
    Nothing >>= f = Nothing  
    Just x >>= f  = f x  
    fail _ = Nothing
```

`return`跟`pure`是等价的。这没什么困难的。我们跟我们在定义`Applicative`的时候做一样的事，只是把他用`Just`包起来。

`>>=`跟我们的`applyMaybe`是一样的。当我们将`Maybe a`塞给我们的函数，我们保留住context，并且在输入是`Nothing`的时候回传`Nothing`。毕竟当没有值的时候套用我们的函数是没有意义的。当输入是`Just`的时候则套用`f`并将他包在`Just`里面。

我们可以试着感觉一下`Maybe`是怎样表现成Monad的。

```haskell
ghci> return "WHAT" :: Maybe String  
Just "WHAT"  
ghci> Just 9 >>= \x -> return (x*10)  
Just 90  
ghci> Nothing >>= \x -> return (x*10)  
Nothing
```

第一行没什么了不起，我们已经知道 `return` 就是 `pure` 而我们又对 `Maybe` 操作过 `pure` 了。至于下两行就比较有趣点。

留意我们是如何把 `Just 9` 喂给 `\x -> return (x*10)`。在函数中 `x` 绑定到 `9`。他看起好像我们能不用 pattern matching 的方式就从 `Maybe` 中抽取出值。但我们并没有丧失掉 `Maybe` 的 context，当他是 `Nothing` 的时候，`>>=` 的结果也会是 `Nothing`。

## 走钢索

![](/files/-MlVFmZmzhRzNG9vxgt7)

我们已经知道要如何把 `Maybe a` 喂进 `a -> Maybe b` 这样的函数。我们可以看看我们如何重复使用 `>>=` 来处理多个 `Maybe a` 的值。

首先来说个小故事。皮尔斯决定要辞掉他的工作改行试着走钢索。他对走钢索蛮在行的，不过仍有个小问题。就是鸟会停在他拿的平衡竿上。他们会飞过来停一小会儿，然后再飞走。这样的情况在两边的鸟的数量一样时并不是个太大的问题。但有时候，所有的鸟都会想要停在同一边，皮尔斯就失去了平衡，就会让他从钢索上掉下去。

我们这边假设两边的鸟差异在三个之内的时候，皮尔斯仍能保持平衡。所以如果是右边有一只，左边有四只的话，那还撑得住。但如果左边有五只，那就会失去平衡。

我们要写个程序来仿真整个情况。我们想看看皮尔斯究竟在好几只鸟来来去去后是否还能撑住。例如说，我们想看看先来了一只鸟停在左边，然后来了四只停在右边，然后左边那只飞走了。之后会是什么情形。

我们用一对整数来代表我们的平衡竿状态。头一个位置代表左边的鸟的数量，第二个位置代表右边的鸟的数量。

```haskell
type Birds = Int  
type Pole = (Birds,Birds)
```

由于我们用整数来代表有多少只鸟，我们便先来定义 `Int` 的同义型态，叫做 `Birds`。然后我们把 `(Birds, Birds)` 定义成 `Pole`。

接下来，我们定义一个函数他接受一个数字，然后把他放在竿子的左边，还有另外一个函数放在右边。

```haskell
landLeft :: Birds -> Pole -> Pole  
landLeft n (left,right) = (left + n,right)  

landRight :: Birds -> Pole -> Pole  
landRight n (left,right) = (left,right + n)
```

我们来试着执行看看：

```haskell
ghci> landLeft 2 (0,0)  
(2,0)  
ghci> landRight 1 (1,2)  
(1,3)  
ghci> landRight (-1) (1,2)  
(1,1)
```

要仿真鸟飞走的话我们只要给定一个负数就好了。 由于这些操作是接受 `Pole` 并回传 `Pole`， 所以我们可以把函数串在一起。

```haskell
ghci> landLeft 2 (landRight 1 (landLeft 1 (0,0)))  
(3,1)
```

当我们喂 `(0,0)` 给 `landLeft 1` 时，我们会得到 `(1,0)`。接着我们仿真右边又停了一只鸟，状态就变成 `(1,1)`。最后又有两只鸟停在左边，状态变成 `(3,1)`。我们这边的写法是先写函数名称，然后再套用参数。但如果先写 pole 再写函数名称会比较清楚，所以我们会想定义一个函数

```haskell
x -: f = f x
```

我们能先套用参数然后再写函数名称：

```haskell
ghci> 100 -: (*3)  
300  
ghci> True -: not  
False  
ghci> (0,0) -: landLeft 2  
(2,0)
```

有了这个函数，我们便能写得比较好读一些：

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 1 -: landLeft 2  
(3,1)
```

这个范例跟先前的范例是等价的，只不过好读许多。很清楚的看出我们是从 `(0,0)` 开始，然后停了一只在左边，接着右边又有一只，最后左边多了两只。

到目前为止没什么问题，但如果我们要停 10 只在左边呢？

```
ghci> landLeft 10 (0,3)  
(10,3)
```

你说左边有 10 只右边却只有 3 只？那不是早就应该掉下去了？这个例子太明显了，如果换个比较不明显的例子。

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)  
(0,2)
```

表面看起来没什么问题，但如果你仔细看的话，有一瞬间是右边有四只，但左边没有鸟。要修正这个错误，我们要重新查看 `landLeft` 跟 `landRight`。我们其实是希望这些函数产生失败的情况。那就是在维持平衡的时候回传新的 pole，但失败的时候告诉我们失败了。这时候 `Maybe` 就刚刚好是我们要的 context 了。我们用 `Maybe` 重新写一次：

```haskell
landLeft :: Birds -> Pole -> Maybe Pole  
landLeft n (left,right)  
    | abs ((left + n) - right) < 4 = Just (left + n, right)  
    | otherwise                    = Nothing  

landRight :: Birds -> Pole -> Maybe Pole  
landRight n (left,right)  
    | abs (left - (right + n)) < 4 = Just (left, right + n)  
    | otherwise                    = Nothing
```

现在这些函数不回传 `Pole` 而回传 `Maybe Pole` 了。他们仍接受鸟的数量跟旧的的 pole，但他们现在会检查是否有太多鸟会造成皮尔斯失去平衡。我们用 guards 来检查是否有差异超过三的情况。如果没有，那就包一个在 `Just` 中的新的 pole，如果是，那就回传 `Nothing`。

再来执行看看：

```haskell
ghci> landLeft 2 (0,0)  
Just (2,0)  
ghci> landLeft 10 (0,3)  
Nothing
```

一如预期，当皮尔斯不会掉下去的时候，我们就得到一个包在 `Just` 中的新 pole。当太多鸟停在同一边的时候，我们就会拿到 `Nothing`。这样很棒，但我们却不知道怎么把东西串在一起了。我们不能做 `landLeft 1 (landRight 1 (0,0))`，因为当我们对 `(0,0)` 使用 `landRight 1` 时，我们不是拿到 `Pole` 而是拿到 `Maybe Pole`。`landLeft 1` 会拿到 `Pole` 而不是拿到 `Maybe Pole`。

我们需要一种方法可以把拿到的 `Maybe Pole` 塞到拿 `Pole` 的函数中，然后回传 `Maybe Pole`。而我们有 `>>=`，他对 `Maybe` 做的事就是我们要的

```haskell
ghci> landRight 1 (0,0) >>= landLeft 2  
Just (2,1)
```

`landLeft 2` 的型态是 `Pole -> Maybe Pole`。我们不能喂给他 `Maybe Pole` 的东西。而 `landRight 1 (0,0)` 的结果就是 `Maybe Pole`，所以我们用 `>>=` 来接受一个有 context 的值然后拿给 `landLeft 2`。`>>=` 的确让我们把 `Maybe` 当作有 context 的值，因为当我们丢 `Nothing` 给 `landLeft 2` 的时候，结果会是 `Nothing`。

```
ghci> Nothing >>= landLeft 2  
Nothing
```

这样我们可以把这些新写的用 `>>=` 串在一起。让 monadic value 可以喂进只吃普通值的函数。

来看看些例子：

```haskell
ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2  
Just (2,4)
```

我们最开始用 `return` 回传一个 pole 并把他包在 `Just` 里面。我们可以像往常套用 `landRight 2`，不过我们不那么做，我们改用 `>>=`。`Just (0,0)` 被喂到 `landRight 2`，得到 `Just (0,2)`。接着被喂到 `landLeft 2`，得到 `Just (2,2)`。

还记得我们之前引入失败情况的例子吗？

```haskell
ghci> (0,0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)  
(0,2)
```

之前的例子并不会反应失败的情况。但如果我们用 `>>=` 的话就可以得到失败的结果。

```haskell
ghci> return (0,0) >>= landLeft 1 >>= landRight 4 >>= landLeft (-1) >>= landRight (-2)  
Nothing
```

![](/files/-MlVFmZq4EpeF4ai6321)

正如预期的，最后的情形代表了失败的情况。我们再进一步看看这是怎么产生的。首先 `return` 把 `(0,0)` 放到一个最小的 context 中，得到 `Just (0,0)`。然后是 `Just (0.0) >>= landLeft 1`。由于 `Just (0,0)` 是一个 `Just` 的值。`landLeft 1` 被套用至 `(0,0)` 而得到 `Just (1,0)`。这反应了我们仍保持在平衡的状态。接着是 `Just (1,0) >>= landright 4` 而得到了 `Just (1,4)`。距离不平衡只有一步之遥了。他又被喂给 `landLeft (-1)`，这组合成了 `landLeft (-1) (1,4)`。由于失去了平衡，我们变得到了 `Nothing`。而我们把 `Nothing` 喂给 `landRight (-2)`，由于他是 `Nothing`，也就自动得到了 `Nothing`。

如果只把 `Maybe` 当作 applicative 用的话是没有办法达到我们要的效果的。你试着做一遍就会卡住。因为 applicative functor 并不允许 applicative value 之间有弹性的交互。他们最多就是让我们可以用 applicative style 来传递参数给函数。applicative operators 能拿到他们的结果并把他用 applicative 的方式喂给另一个函数，并把最终的 applicative 值放在一起。但在每一步之间并没有太多允许我们作手脚的机会。而我们的范例需要每一步都倚赖前一步的结果。当每一只鸟降落的时候，我们都会把前一步的结果拿出来看看。好知道结果到底应该成功或失败。

我们也能写出一个函数，完全不管现在究竟有几只鸟停在竿子上，只是要害皮尔斯滑倒。我们可以称呼这个函数叫做 `banana`：

```haskell
banana :: Pole -> Maybe Pole  
banana _ = Nothing
```

现在我们能把香蕉皮串到我们的过程中。他绝对会让遇到的人滑倒。他完全不管前面的状态是什么都会产生失败。

```haskell
ghci> return (0,0) >>= landLeft 1 >>= banana >>= landRight 1  
Nothing
```

`Just (1,0)` 被喂给 `banana`，而产生了 `Nothing`，之后所有的结果便都是 `Nothing` 了。

要同样表示这种忽略前面的结果，只注重眼前的 monadic value 的情况，其实我们可以用 `>>` 来表达。

```haskell
(>>) :: (Monad m) => m a -> m b -> m b  
m >> n = m >>= \_ -> n
```

一般来讲，碰到一个完全忽略前面状态的函数，他就应该只会回传他想回传的值而已。但碰到 Monad，他们的 context 还是必须要被考虑到。来看一下 `>>` 串接 `Maybe` 的情况。

```haskell
ghci> Nothing >> Just 3  
Nothing  
ghci> Just 3 >> Just 4  
Just 4  
ghci> Just 3 >> Nothing  
Nothing
```

如果你把 `>>` 换成 `>>= \_ ->`，那就很容易看出他的意思。

我们也可以把 `banana` 改用 `>>` 跟 `Nothing` 来表达：

```haskell
ghci> return (0,0) >>= landLeft 1 >> Nothing >>= landRight 1  
Nothing
```

我们得到了保证的失败。

我们也可以看看假如我们故意不用把 `Maybe` 视为有 context 的值的写法。他会长得像这样：

```haskell
routine :: Maybe Pole  
routine = case landLeft 1 (0,0) of  
    Nothing -> Nothing  
    Just pole1 -> case landRight 4 pole1 of   
            Nothing -> Nothing  
            Just pole2 -> case landLeft 2 pole2 of  
                    Nothing -> Nothing  
                    Just pole3 -> landLeft 1 pole3
```

![](/files/-MlVFmZt8MDpUw28Cyhu)

左边先停了一只鸟，然后我们停下来检查有没有失败。当失败的时候我们回传 `Nothing`。当成功的时候，我们在右边停一只鸟，然后再重复前面做的事情。把这些琐事转换成 `>>=` 证明了 `Maybe` Monad 的力量，可以省去我们不少的时间。

注意到 `Maybe` 对 `>>=` 的实作，他其实就是在做碰到 `Nothing` 就会传 `Nothing`，碰到正确值就继续用 `Just` 传递值。

在这个章节中，我们看过了好几个函数，也见识了用 `Maybe` monad 来表示失败的 context 的力量。把普通的函数套用换成了 `>>=`，让我们可以轻松地应付可能会失败的情况，并帮我们传递 context。这边的 context 就代表失败的可能性，当我们套用函数到 context 的时候，就代表考虑进了失败的情况。

## do 表示法

Monad 在 Haskell 中是十分重要的，所以我们还特别为了操作他设置了特别的语法：`do` 表示法。我们在介绍 I/O 的时候已经用过 `do` 来把小的 I/O action 串在一起了。其实 `do` 并不只是可以用在 `IO`，他可以用在任何 monad 上。他的原则是简单明了，把 monadic value 串成一串。我们这边来细看 `do` 是如何使用，以及为什么我们十分倚赖他。

来看一下熟悉的例子：

```haskell
ghci> Just 3 >>= (\x -> Just (show x ++ "!"))  
Just "3!"
```

你说这没什么了不起，不过就是把 monadic value 喂给一个函数罢了。其中 `x` 就指定成 `3`。也从 monadic value 变成了普通值。那如果我们要在 lambda 中使用 `>>=` 呢？

```haskell
ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))  
Just "3!"
```

我们嵌一个 `>>=` 在另外一个 `>>=` 中。在外层的 lambda，我们把 `Just "!"` 喂给 `\y -> Just (show x ++ y)`。在内层的 lambda，`y` 被指定成 `"!"`。`x` 仍被指定成 `3`，是因为我们是从外层的 lambda 取值的。这些行为让我们回想到下列式子：

```haskell
ghci> let x = 3; y = "!" in show x ++ y  
"3!"
```

差别在于前述的值是 monadic，具有失败可能性的 context。我们可以把其中任何一步代换成失败的状态：

```haskell
ghci> Nothing >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))  
Nothing  
ghci> Just 3 >>= (\x -> Nothing >>= (\y -> Just (show x ++ y)))  
Nothing  
ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Nothing))  
Nothing
```

第一行中，把 `Nothing` 喂给一个函数，很自然地会回传 `Nothing`。第二行里，我们把 `Just 3` 喂给一个函数，所以 `x` 就成了 `3`。但我们把 `Nothing` 喂给内层的 lambda 所有的结果就成了 `Nothing`，这也进一步使得外层的 lambda 成了 `Nothing`。这就好比我们在 `let` expression 中来把值指定给变量一般。只差在我们这边的值是 monadic value。

要再说得更清楚点，我们来把 script 改写成每行都处理一个 `Maybe`：

```haskell
foo :: Maybe String  
foo = Just 3   >>= (\x -> 
      Just "!" >>= (\y -> 
      Just (show x ++ y)))
```

为了摆脱这些烦人的 lambda，Haskell 允许我们使用 `do` 表示法。他让我们可以把先前的程序写成这样：

```haskell
foo :: Maybe String  
foo = do  
    x <- Just 3  
    y <- Just "!"  
    Just (show x ++ y)
```

![](/files/-LjUmqeIT7ufvbLOykVW)

这看起来好像让我们不用在每一步都去检查 `Maybe` 的值究竟是 `Just` 或 `Nothing`。这蛮方便的，如果在任何一个步骤我们取出了 `Nothing`。那整个 `do` 的结果就会是 `Nothing`。我们把整个责任都交给 `>>=`，他会帮我们处理所有 context 的问题。这边的 `do` 表示法不过是另外一种语法的形式来串连所有的 monadic value 罢了。

在 `do` expression 中，每一行都是一个 monadic value。要检查处理的结果的话，就要使用 `<-`。如果我们拿到一个 `Maybe String`，并用 `<-` 来绑定给一个变量，那个变量就会是一个 `String`，就像是使用 `>>=` 来将 monadic value 带给 lambda 一样。至于 `do` expression 中的最后一个值，好比说 `Just (show x ++ y)`，就不能用 `<-` 来绑定结果，因为那样的写法当转换成 `>>=` 的结果时并不合理。他必须要是所有 monadic value 黏起来后的总结果，要考虑到前面所有可能失败的情形。

举例来说，来看看下面这行：

```haskell
ghci> Just 9 >>= (\x -> Just (x > 8))  
Just True
```

由于 `>>=` 左边的参数是一个 `Just` 型态的值，当 lambda 被套用至 `9` 就会得到 `Just True`。如果我们重写整个式子，改用 `do` 表示法：我们会得到：

```haskell
marySue :: Maybe Bool  
marySue = do   
    x <- Just 9  
    Just (x > 8)
```

如果我们比较这两种写法，就很容易看出为什么整个 monadic value 的结果会是在 `do` 表示法中最后一个 monadic value 的值。他串连了全面所有的结果。

我们走钢索的仿真程序也可以改用 `do` 表示法重写。`landLeft` 跟 `landRight` 接受一个鸟的数字跟一个竿子来产生一个包在 `Just` 中新的竿子。而在失败的情况会产生 `Nothing`。我们使用 `>>=` 来串连所有的步骤，每一步都倚赖前一步的结果，而且都带有可能失败的 context。这边有一个范例，先是有两只鸟停在左边，接着有两只鸟停在右边，然后是一只鸟停在左边：

```haskell
routine :: Maybe Pole  
routine = do  
    start <- return (0,0)  
    first <- landLeft 2 start  
    second <- landRight 2 first  
    landLeft 1 second
```

我们来看看成功的结果：

```haskell
ghci> routine  
Just (3,2)
```

当我们要把这些 routine 用具体写出的 `>>=`，我们会这样写：`return (0,0) >>= landLeft 2`，而有了 `do` 表示法，每一行都必须是一个 monadic value。所以我们清楚地把前一个 `Pole` 传给 `landLeft` 跟 `landRight`。如果我们查看我们绑定 `Maybe` 的变量，`start` 就是 `(0,0)`，而 `first` 就会是 `(2,0)`。

由于 `do` 表示法是一行一行写，他们会看起来很像是命令式的写法。但实际上他们只是代表串行而已，每一步的值都倚赖前一步的结果，并带着他们的 context 继续下去。

我们再重新来看看如果我们没有善用 `Maybe` 的 monad 性质的程序：

```haskell
routine :: Maybe Pole  
    routine =   
        case Just (0,0) of   
            Nothing -> Nothing  
            Just start -> case landLeft 2 start of  
                Nothing -> Nothing  
                Just first -> case landRight 2 first of  
                    Nothing -> Nothing  
                    Just second -> landLeft 1 second
```

在成功的情形下，`Just (0,0)` 变成了 `start`， 而 `landLeft 2 start` 的结果成了 `first`。

如果我们想在 `do` 表示法里面对皮尔斯丢出香蕉皮，我们可以这样做：

```haskell
routine :: Maybe Pole  
routine = do  
    start <- return (0,0)  
    first <- landLeft 2 start  
    Nothing  
    second <- landRight 2 first  
    landLeft 1 second
```

当我们在 `do` 表示法写了一行运算，但没有用到 `<-` 来绑定值的话，其实实际上就是用了 `>>`，他会忽略掉计算的结果。我们只是要让他们有序，而不是要他们的结果，而且他比写成 `_ <- Nothing` 要来得漂亮的多。

你会问究竟我们何时要使用 `do` 表示法或是 `>>=`，这完全取决于你的习惯。在这个例子由于有每一步都倚赖于前一步结果的特性，所以我们使用 `>>=`。如果用 `do` 表示法，我们就必须清楚写出鸟究竟是停在哪根竿子上，但其实每一次都是前一次的结果。不过他还是让我们了解到怎么使用 `do`。

在 `do` 表示法中，我们其实可以用模式匹配来绑定 monadic value，就好像我们在 `let` 表达式，跟函数参数中使用模式匹配一样。这边来看一个在 `do` 表示法中使用模式匹配的范例：

```haskell
justH :: Maybe Char  
justH = do  
    (x:xs) <- Just "hello"  
    return x
```

我们用模式匹配来取得 `"hello"` 的第一个字符，然后回传结果。所以 `justH` 计算会得到 `Just 'h'`。

如果模式匹配失败怎么办？当定义一个函数的时候，一个模式不匹配就会跳到下一个模式。如果所有都不匹配，那就会造成错误，整个程序就当掉。另一方面，如果在 `let` 中进行模式匹配失败会直接造成错误。毕竟在 `let` 表达式的情况下并没有失败就跳下一个的设计。至于在 `do` 表示法中模式匹配失败的话，那就会调用 `fail` 函数。他定义在 `Monad` 的 type class 定义猪。他允许在现在的 monad context 底下，失败只会造成失败而不会让整个程序当掉。他缺省的实作如下：

```haskell
fail :: (Monad m) => String -> m a  
fail msg = error msg
```

可见缺省的实作的确是让程序挂掉，但在某些考虑到失败的可能性的 Monad（像是 `Maybe`）常常会有他们自己的实作。对于 `Maybe`，他的实作像是这样：

```haskell
fail _ = Nothing
```

他忽略错误消息，并直接回传 `Nothing`。所以当在 `do` 表示法中的 `Maybe` 模式匹配失败的时候，整个结果就会是 `Nothing`。这种方式比起让程序挂掉要好多了。这边来看一下 `Maybe` 模式匹配失败的范例：

```haskell
wopwop :: Maybe Char  
wopwop = do  
    (x:xs) <- Just ""  
    return x
```

模式匹配的失败，所以那一行的效果相当于一个 `Nothing`。我们来看看执行结果：

```haskell
ghci> wopwop  
Nothing
```

这样模式匹配的失败只会限制在我们 monad 的 context 中，而不是整个程序的失败。这种处理方式要好多了。

## List Monad

![](/files/-MlVFm_6_5CYqdwzHqeV)

我们已经了解了 `Maybe` 可以被看作具有失败可能性 context 的值，也见识到如何用 `>>=` 来把这些具有失败考量的值传给函数。在这一个章节中，我们要看一下如何利用 list 的 monadic 的性质来写 non-deterministic 的程序。

我们已经讨论过在把 list 当作 applicatives 的时候他们具有 non-deterministic 的性质。像 `5` 这样一个值是 deterministic 的。他只有一种结果，而且我们清楚的知道他是什么结果。另一方面，像 `[3,8,9]` 这样的值包含好几种结果，所以我们能把他看作是同时具有好几种结果的值。把 list 当作 applicative functors 展示了这种特性：

```haskell
ghci> (*) <$> [1,2,3] <*> [10,100,1000]  
[10,100,1000,20,200,2000,30,300,3000]
```

将左边 list 中的元素乘上右边 list 中的元素这样所有的组合全都被放进结果的 list 中。当处理 non-determinism 的时候，这代表我们有好几种选择可以选，我们也会每种选择都试试看，因此最终的结果也会是一个 non-deterministic 的值。只是包含更多不同可能罢了。

non-determinism 这样的 context 可以被漂亮地用 monad 来考虑。所以我们这就来看看 list 的 `Monad` instance 的定义：

```haskell
instance Monad [] where  
    return x = [x]  
    xs >>= f = concat (map f xs)  
    fail _ = []
```

`return` 跟 `pure` 是做同样的事，所以我们应该算已经理解了 `return` 的部份。他接受一个值，并把他放进一个最小的一个 context 中。换种说法，就是他做了一个只包含一个元素的 list。这样对于我们想要操作普通值的时候很有用，可以直接把他包起来变成 non-deterministic value。

要理解 `>>=` 在 list monad 的情形下是怎么运作的，让我们先来回归基本。`>>=` 基本上就是接受一个有 context 的值，把他喂进一个只接受普通值的函数，并回传一个具有 context 的值。如果操作的函数只会回传普通值而不是具有 context 的值，那 `>>=` 在操作一次后就会失效，因为 context 不见了。让我们来试着把一个 non-deterministic value 塞到一个函数中：

```haskell
ghci> [3,4,5] >>= \x -> [x,-x]  
[3,-3,4,-4,5,-5]
```

当我们对 `Maybe` 使用 `>>=`，是有考虑到可能失败的 context。在这边 `>>=` 则是有考虑到 non-determinism。`[3,4,5]` 是一个 non-deterministic value，我们把他喂给一个回传 non-deterministic value 的函数。那结果也会是 non-deterministic。而且他包含了所有从 `[3,4,5]` 取值，套用 `\x -> [x,-x]` 后的结果。这个函数他接受一个数值并产生两个数值，一个原来的数值与取过负号的数值。当我们用 `>>=` 来把一个 list 喂给这个函数，所有在 list 中的数值都保留了原有的跟取负号过的版本。`x` 会针对 list 中的每个元素走过一遍。

要看看结果是如何算出来的，只要看看实作就好了。首先我们从 `[3,4,5]` 开始。然后我们用 lambda 映射过所有元素得到：

```haskell
[[3,-3],[4,-4],[5,-5]]
```

lambda 会扫过每个元素，所以我们有一串包含一堆 list 的 list，最后我们在把这些 list 压扁，得到一层的 list。这就是我们得到 non-deterministic value 的过程。

non-determinism 也有考虑到失败的可能性。`[]` 其实等价于 `Nothing`，因为他什么结果也没有。所以失败等同于回传一个空的 list。所有的错误消息都不用。让我们来看看范例：

```haskell
ghci> [] >>= \x -> ["bad","mad","rad"]  
[]  
ghci> [1,2,3] >>= \x -> []  
[]
```

第一行里面，一个空的 list 被丢给 lambda。因为 list 没有任何元素，所以函数收不到任何东西而产生空的 list。这跟把 `Nothing` 喂给函数一样。第二行中，每一个元素都被喂给函数，但所有元素都被丢掉，而只回传一个空的 list。因为所有的元素都造成了失败，所以整个结果也代表失败。

就像 `Maybe` 一样，我们可以用 `>>=` 把他们串起来：

```haskell
ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
```

![](/files/-MlVFm_8j1AyNPmobV1s)

`[1,2]` 被绑定到 `n` 而 `['a','b']` 被绑定到 `ch`。最后我们用 `return (n,ch)` 来把他放到一个最小的 context 中。在这个案例中，就是把 `(n,ch)` 放到 list 中，这代表最低程度的 non-determinism。整套结构要表达的意思就是对于 `[1,2]` 的每个元素，以及 `['a','b']` 的每个元素，我们产生一个 tuple，每项分别取自不同的 list。

一般来说，由于 `return` 接受一个值并放到最小的 context 中，他不会多做什么额外的东西仅仅是展示出结果而已。

```
当你要处理 non-deterministic value 的时候，你可以把 list 中的每个元素想做计算路线的一个 branch。
```

这边把先前的表达式用 `do` 重写：

```haskell
listOfTuples :: [(Int,Char)]  
listOfTuples = do  
    n <- [1,2]  
    ch <- ['a','b']  
    return (n,ch)
```

这样写可以更清楚看到 `n` 走过 `[1,2]` 中的每一个值，而 `ch` 则取过 `['a','b']` 中的每个值。正如 `Maybe` 一般，我们从 monadic value 中取出普通值然后喂给函数。`>>=` 会帮我们处理好一切 context 相关的问题，只差在这边的 context 指的是 non-determinism。

使用 `do` 来对 list 操作让我们回想起之前看过的一些东西。来看看下列的片段：

```haskell
ghci> [ (n,ch) | n <- [1,2], ch <- ['a','b'] ]  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
```

没错，就是 list comprehension。在先前的范例中，`n` 会走过 `[1,2]` 的每个元素，而 `ch` 会走过 `['a','b']` 的每个元素。同时我们又把 `(n,ch)` 放进一个 context 中。这跟 list comprehension 的目的一样，只是我们在 list comprehension 里面不用在最后写一个 `return` 来得到 `(n,ch)` 的结果。

实际上，list comprehension 不过是一个语法糖。不论是 list comprehension 或是用 `do` 表示法来表示，他都会转换成用 `>>=` 来做计算。

List comprehension 允许我们 filter 我们的结果。举例来说，我们可以只要包含 `7` 在表示位数里面的数值。

```haskell
ghci> [ x | x <- [1..50], '7' `elem` show x ]  
[7,17,27,37,47]
```

我们用 `show` 跟 `x` 来把数值转成字串，然后检查 `'7'` 是否包含在字串里面。要看看 filtering 要如何转换成用 list monad 来表达，我们可以考虑使用 `guard` 函数，还有 `MonadPlus` 这个 type class。`MonadPlus` 这个 type class 是用来针对可以同时表现成 monoid 的 monad。下面是他的定义：

```haskell
class Monad m => MonadPlus m where  
    mzero :: m a  
    mplus :: m a -> m a -> m a
```

`mzero` 是其实是 `Monoid` 中 `mempty` 的同义词，而 `mplus` 则对应到 `mappend`。因为 list 同时是 monoid 跟 monad，他们可以是 `MonadPlus` 的 instance。

```haskell
instance MonadPlus [] where  
    mzero = []  
    mplus = (++)
```

对于 list 而言，`mzero` 代表的是不产生任何结果的 non-deterministic value，也就是失败的结果。而 `mplus` 则把两个 non-deterministic value 结合成一个。`guard` 这个函数被定义成下列形式：

```haskell
guard :: (MonadPlus m) => Bool -> m ()  
guard True = return ()  
guard False = mzero
```

这函数接受一个布尔值，如果他是 `True` 就回传一个包在缺省 context 中的 `()`。如果他失败就产生 mzero。

```haskell
ghci> guard (5 > 2) :: Maybe ()  
Just ()  
ghci> guard (1 > 2) :: Maybe ()  
Nothing  
ghci> guard (5 > 2) :: [()]  
[()]  
ghci> guard (1 > 2) :: [()]  
[]
```

看起来蛮有趣的，但用起来如何呢？我们可以用他来过滤 non-deterministic 的计算。

```haskell
ghci> [1..50] >>= (\x -> guard ('7' `elem` show x) >> return x)  
[7,17,27,37,47]
```

这边的结果跟我们之前 list comprehension 的结果一致。究竟 `guard` 是如何办到的？我们先看看 `guard` 跟 `>>` 是如何交互：

```haskell
ghci> guard (5 > 2) >> return "cool" :: [String]  
["cool"]  
ghci> guard (1 > 2) >> return "cool" :: [String]  
[]
```

如果 `guard` 成功的话，结果就会是一个空的 tuple。接着我们用 `>>` 来忽略掉空的 tuple，而呈现不同的结果。另一方面，如果 `guard` 失败的话，后面的 `return` 也会失败。这是因为用 `>>=` 把空的 list 喂给函数总是会回传空的 list。基本上 `guard` 的意思就是：如果一个布尔值是 `False` 那就产生一个失败状态，不然的话就回传一个基本的 `()`。这样计算就可以继续进行。

这边我们把先前的范例用 `do` 改写：

```haskell
sevensOnly :: [Int]  
sevensOnly = do  
    x <- [1..50]  
    guard ('7' `elem` show x)  
    return x
```

如果我们不写最后一行 `return x`，那整个 list 就会是包含一堆空 tuple 的 list。

把上述范例写成 list comprehension 的话就会像这样：

```haskell
ghci> [ x | x <- [1..50], '7' `elem` show x ]  
[7,17,27,37,47]
```

所以 list comprehension 的 filtering 基本上跟 `guard` 是一致的。

### A knight's quest

这边来看一个可以用 non-determinism 解决的问题。假设你有一个西洋棋盘跟一只西洋棋中的骑士摆在上面。我们希望知道是否这只骑士可以在三步之内移到我们想要的位置。我们只要用一对数值来表示骑士在棋盘上的位置。第一个数值代表棋盘的行，而第二个数值代表棋盘的列。

![](/files/-MlVFm_Az-Zm6iqkE-LK)

我们先帮骑士的位置定义一个 type synonym。

```haskell
type KnightPos = (Int,Int)
```

假设骑士现在是在 `(6,2)`。究竟他能不能够在三步内移动到 `(6,1)` 呢？你可能会先考虑究竟哪一步是最佳的一步。但不如全部一起考虑吧！要好好利用所谓的 non-determinism。所以我们不是只选择一步，而是选择全部。我们先写一个函数回传所有可能的下一步：

```haskell
moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = do  
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
                ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
                ]  
    guard (c' `elem` [1..8] && r' `elem` [1..8])
    return (c',r')
```

骑士有可能水平或垂直移动一步或二步，但问题是他们必须要同时水平跟垂直移动。`(c',r')` 走过 list 中的每一个元素，而 `guard` 会保证产生的结果会停留在棋盘上。如果没有，那就会产生一个空的 list，表示失败的结果，`return (c',r')` 也就不会被执行。

这个函数也可以不用 list monad 来写，但我们这边只是写好玩的。下面是一个用 `filter` 实现的版本：

```haskell
moveKnight :: KnightPos -> [KnightPos]  
moveKnight (c,r) = filter onBoard  
    [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)  
    ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)  
    ]  
    where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]
```

两个函数做的都是相同的事，所以选个你喜欢的吧。

```haskell
ghci> moveKnight (6,2)  
[(8,1),(8,3),(4,1),(4,3),(7,4),(5,4)]  
ghci> moveKnight (8,1)  
[(6,2),(7,3)]
```

我们接受一个位置然后产生所有可能的移动方式。所以我们有一个 non-deterministic 的下一个位置。我们用 `>>=` 来喂给 `moveKnight`。接下来我们就可以写一个三步内可以达到的所有位置：

```haskell
in3 :: KnightPos -> [KnightPos]  
in3 start = do   
    first <- moveKnight start  
    second <- moveKnight first  
    moveKnight second
```

如果你传 `(6,2)`，得到的 list 会很大，因为会有不同种方式来走到同样的一个位置。我们也可以不用 `do` 来写：

```haskell
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
```

第一次 `>>=` 给我们移动一步的所有结果，第二次 `>>=` 给我们移动两步的所有结果，第三次则给我们移动三步的所有结果。

用 `return` 来把一个值放进缺省的 context 然后用 `>>=` 喂给一个函数其实跟函数调用是同样的，只是用不同的写法而已。 接着我们写一个函数接受两个位置，然后可以测试是否可以在三步内从一个位置移到另一个位置：

```haskell
canReachIn3 :: KnightPos -> KnightPos -> Bool  
canReachIn3 start end = end `elem` in3 start
```

我们产生所有三步的可能位置，然后看看其中一个位置是否在里面。所以我们可以看看是否可以在三步内从 `(6,2)` 走到 `(6,1)`：

```haskell
ghci> (6,2) `canReachIn3` (6,1)  
True
```

那从 `(6,2)` 到 `(7,3)` 呢？

```haskell
ghci> (6,2) `canReachIn3` (7,3)  
False
```

答案是不行。你可以修改函数改成当可以走到的时候，他还会告诉你实际的步骤。之后你也可以改成不只限定成三步，可以任意步。

## Monad laws (单子律)

![](/files/-MlVFm_C1u6HUDETnSAX)

正如 applicative functors 以及 functors，Monad 也有一些要遵守的定律。我们定义一个 `Monad` 的 instance 并不代表他是一个 monad，只代表他被定义成那个 type class 的 instance。一个型态要是 monad，则必须遵守单子律。这些定律让我们可以对这个型态的行为做一些合理的假设。

Haskell 允许任何型态是任何 type class 的 instance。但他不会检查单子律是否有被遵守，所以如果我们要写一个 `Monad` 的 instance，那最好我们确定他有遵守单子律。我们可以不用担心标准函式库中的型态是否有遵守单子律。但之后我们定义自己的型态时，我们必须自己检查是否有遵守单子律。不用担心，他们不会很复杂。

### Left identity

单子律的第一项说当我们接受一个值，将他用 `return` 放进一个缺省的 context 并把他用 `>>=` 喂进一个函数的结果，应该要跟我们直接做函数调用的结果一样。

* `retrun x >>= f` 应该等于 `f x`

如果你是把 monadic value 视为把一个值放进最小的 context 中，仅仅是把同样的值放进结果中的话， 那这个定律应该很直觉。因为把这个值放进 context 中然后丢给函数，应该要跟直接把这个值丢给函数做调用应该没有差别。

对于 `Maybe` monad，`return` 被定义成 `Just`。`Maybe` monad 讲的是失败的可能性，如果我们有普通值要把他放进 context 中，那把这个动作当作是计算成功应该是很合理的，毕竟我们都知道那个值是很具体的。这边有些范例：

```haskell
ghci> return 3 >>= (\x -> Just (x+100000))  
Just 100003  
ghci> (\x -> Just (x+100000)) 3  
Just 100003
```

对于 list monad 而言，`return` 是把值放进一个 list 中，变成只有一个元素的 list。`>>=` 则会走过 list 中的每个元素，并把他们丢给函数做运算，但因为在单一元素的 list 中只有一个值，所以跟直接对那元素做运算是等价的：

```haskell
ghci> return "WoM" >>= (\x -> [x,x,x])  
["WoM","WoM","WoM"]  
ghci> (\x -> [x,x,x]) "WoM"  
["WoM","WoM","WoM"]
```

至于 `IO`，我们已经知道 `return` 并不会造成副作用，只不过是在结果中呈现原有值。所以这个定律对于 `IO` 也是有效的。

### Right identity

单子律的第二个规则是如果我们有一个 monadic value，而且我们把他用 `>>=` 喂给 `return`，那结果就会是原有的 monadic value。

* `m >>= return` 会等于 `m`

这一个可能不像第一定律那么明显，但我们还是来看看为什么会遵守这条。当我们把一个 monadic value 用 `>>=` 喂给函数，那些函数是接受普通值并回传具有 context 的值。`return` 也是在他们其中。如果你仔细看他的型态，`return` 是把一个普通值放进一个最小 context 中。这就表示，对于 `Maybe` 他并没有造成任何失败的状态，而对于 list 他也没有多加 non-determinism。

```haskell
ghci> Just "move on up" >>= (\x -> return x)  
Just "move on up"  
ghci> [1,2,3,4] >>= (\x -> return x)  
[1,2,3,4]  
ghci> putStrLn "Wah!" >>= (\x -> return x)  
Wah!
```

如果我们仔细查看 list monad 的范例，会发现 `>>=` 的实作是：

```haskell
xs >>= f = concat (map f xs)
```

所以当我们将 `[1,2,3,4]` 丢给 `return`，第一个 `return` 会把 `[1,2,3,4]` 映射成 `[[1],[2],[3],[4]]`，然后再把这些小 list 串接成我们原有的 list。

Left identity 跟 right identity 是描述 `return` 的行为。他重要的原因是因为他把普通值转换成具有 context 的值，如果他出错的话会很头大。

### Associativity

单子律最后一条是说当我们用 `>>=` 把一串 monadic function 串在一起，他们的先后顺序不应该影响结果：

* `(m >>= f) >>= g` 跟 `m >>= (\x -> f x >>= g)` 是相等的

究竟这边说的是什么呢？我们有一个 monadic value `m`，以及两个 monadic function `f` 跟 `g`。当我们写下 `(m >>= f) >>= g`，代表的是我们把 `m` 喂给 `f`，他的结果是一个 monadic value。然后我们把这个结果喂给 `g`。而在 `m >>= (\x -> f x >>= g)` 中，我们接受一个 monadic value 然后喂给一个函数，这个函数会把 `f x` 的结果丢给 `g`。我们不太容易直接看出两者相同，所以先来看个范例比较好理解。

还记得之前皮尔斯的范例吗？要仿真鸟停在他的平衡竿上，我们把好几个函数串在一起

```haskell
ghci> return (0,0) >>= landRight 2 >>= landLeft 2 >>= landRight 2  
Just (2,4)
```

从 `Just (0,0)` 出发，然后把值传给 `landRight 2`。他的结果又被绑到下一个 monadic function，以此类推。如果我们用括号清楚标出优先级的话会是这样：

```haskell
ghci> ((return (0,0) >>= landRight 2) >>= landLeft 2) >>= landRight 2  
Just (2,4)
```

我们也可以改写成这样：

```haskell
return (0,0) >>= (\x -> 
landRight 2 x >>= (\y -> 
landLeft 2 y >>= (\z -> 
landRight 2 z)))
```

`return (0,0)` 等价于 `Just (0,0)`，当我们把他喂给 lambda，里面的 `x` 就等于 `(0,0)`。`landRight` 接受一个数值跟 pole，算出来的结果是 `Just (0,2)` 然后把他喂给另一个 lambda，里面的 `y` 就变成了 `(0,2)`。这样的操作持续下去，直到最后一只鸟降落，而得到 `Just (2,4)` 的结果，这也是整个操作的总结果。

这些 monadic function 的优先级并不重要，重点是他们的意义。从另一个角度来看这个定律：考虑两个函数 `f` 跟 `g`，将两个函数组合起来的定义像是这样：

```haskell
(.) :: (b -> c) -> (a -> b) -> (a -> c)  
f . g = (\x -> f (g x))
```

如果 `g` 的型态是 `a -> b` 且 `f` 的型态是 `b -> c`，我们可以把他们合成一个型态是 `a -> c` 的新函数。所以中间的参数都有自动带过。现在假设这两个函数是 monadic function，也就是说如果他们的回传值是 monadic function？如果我们有一个函数他的型态是 `a -> m b`，我们并不能直接把结果丢给另一个型态为 `b -> m c` 的函数，因为后者只接受型态为 `b` 的普通值。然而，我们可以用 `>>=` 来做到我们想要的事。有了 `>>=`，我们可以合成两个 monadic function：

```haskell
(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)  
f <=< g = (\x -> g x >>= f)
```

所以现在我们可以合成两个 monadic functions：

```haskell
ghci> let f x = [x,-x]  
ghci> let g x = [x*3,x*2]  
ghci> let h = f <=< g  
ghci> h 3  
[9,-9,6,-6]
```

至于这跟结合律有什么关系呢？当我们把这定律看作是合成的定律，他就只是说了 `f <=< (g <=< h)` 跟 `(f <=< g) <=< h` 应该等价。只是他是针对 monad 而已。

如果我们把头两个单子律用 `<=<` 改写，那 left identity 不过就是说对于每个 monadic function `f`，`f <=< return` 跟 `f` 是等价，而 right identity 说 `return <=< f` 跟 `f` 是等价。

如果看看普通函数的情形，就会发现很像，`(f . g) . h` 等价于 `f . (g . h)`，`f . id` 跟 `f` 等价，且 `id . f` 等价于 `f`。

在这一章中，我们查看了 monad 的基本性质，而且也了解了 `Maybe` monad 跟 list monad 的运作方式。在下一章，我们会看看其他一些有特色的 monad，我们也会学到如何定义自己的 monad。


# ch13


# 再来看看更多 Monad

![](/files/-MlVFmXsZodbNYBzHBoA)

我们已经看过 Monad 是如何接受具有 context 的值，并如何用函数操作他们 还有如何用 `>>=` 跟 `do` 来减轻我们对 context 的关注，集中精神在 value 本身。

我们也看过了 `Maybe` 是如何把值加上一个可能会失败的 context。我们学习到 List Monad 是如何加进多重结果的 context。我们也了解 `IO` Monad 如何运作，而且我们在知道什么是 Monad 之前就已经知道他了。

在这个章节，我们会介绍一些其他的 Monad。他们可以把值变成 monadic value，因此可以让我们的程序更简洁清晰。多见识几个 Monad 也可以敏锐我们对 Monad 的直觉。

我们即将要介绍的 Monad 都包含在 `mtl` 这个套建中。一个 Haskell package 包含了一堆模块。而 `mtl` 已经包含在 Haskell Platform 中，所以你可能不用另外安装。要检查你有没有这套件，你可以下 `ghc-pkg list`。这会列出你已经安装的套件，其中应该包含 `mtl` 后面接着对应的版号。

## 你所不知道的 Writer Monad

我们已经看过 `Maybe`, list 以及 `IO` Monad。现在我们要来看看 `Writer` Monad。

相对于 `Maybe` 是加入可能失败的 context，list 是加入 non-deterministic 的 context，`Writer` 则是加进一个附加值的 context，好比 log 一般。`Writer` 可以让我们在计算的同时搜集所有 log 纪录，并汇集成一个 log 并附加在结果上。

例如我们想要附加一个 String 好说明我们的值在干么（有可能是为了除错）。想像有一个函数接受一个代表帮派人数的数字，然后会回传值告诉我们这是否算是一个庞大的帮派：

```haskell
isBigGang :: Int -> Bool  
isBigGang x = x > 9
```

现在我们希望他不只是回传 `True` 或 `False`，我们还希望他能够多回传一个字串代表 log。这很容易，只要多加一个 `String` 在 `Bool` 旁边就好了。

```haskell
isBigGang :: Int -> (Bool, String)  
isBigGang x = (x > 9, "Compared gang size to 9.")
```

我们现在回传了一个 Tuple，第一个元素是原来的布尔值，第二个元素是一个 String。现在我们的值有了一个 context。

```haskell
ghci> isBigGang 3  
(False,"Compared gang size to 9.")  
ghci> isBigGang 30  
(True,"Compared gang size to 9.")
```

![](/files/-MlVFmXvhVoVjnckePf6)

到目前为止都还不错，`isBigGang` 回传一个值跟他的 context。对于正常的数值来说这样的写法都能运作良好。但如果我们想要把一个已经具有 context 的值，像是 `(3, "Smallish gang.")`，喂给 `isBigGang` 呢？我们又面对了同样的问题：如果我们有一个能接受正常数值并回传一个具有 context 值的 function，那我们要如何喂给他一个具有 context 的值？

当我们在研究 `Maybe` monad 的时候，我们写了一个 `applyMaybe`。他接受一个 `Maybe a` 值跟一个 `a -> Maybe b` 型态的函数，他会把 `Maybe a` 喂给这个 function，即便这个 function 其实是接受 `a` 而非 `Maybe a`。`applyMaybe` 有针对这样的 context 做处理，也就是会留意有可能发生的失败情况。但在 `a -> Maybe b` 里面，我们可以只专心处理正常数值即可。因为 `applyMaybe` (之后变成了 `>>=`)会帮我们处理需要检查 `Nothing` 或 `Just` 的情况。

我们再来写一个接受附加 log 值的函数，也就是 `(a, String)` 型态的值跟 `a -> (b, String)` 型态的函数。我们称呼这个函数为 `applyLog`。这个函数有的 context 是附加 log 值，而不是一个可能会失败的 context，因此 `applyLog` 会确保原有的 log 被保留，并附上从函数产生出的新的 log。这边我们来看一下实作：

```haskell
applyLog :: (a,String) -> (a -> (b,String)) -> (b,String)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log ++ newLog)
```

当我们想把一个具有 context 的值喂给一个函数的时候，我们会尝试把值跟他的 context 分开，然后把值喂给函数再重新接回 context。在 `Maybe` monad 的情况，我们检查值是否为 `Just x`，如果是，便将 `x` 喂给函数。而在 log 的情况，我们知道 pair 的其中一个 component 是 log 而另一个是值。所以我们先取出值 `x`，将 `f` apply 到 `x`，便获取 `(y,newLog)`，其中 `y` 是新的值而 `newLog` 则是新的 log。但如果我们回传 `newLog`，旧的 log 便不会包含进去，所以我们要回传的是 `(y, log ++ newLog)`。我们用 `++` 来把新的 log 接到旧的上面。

来看看 `applyLog` 运作的情形：

```haskell
ghci> (3, "Smallish gang.") `applyLog` isBigGang  
(False,"Smallish gang.Compared gang size to 9")  
ghci> (30, "A freaking platoon.") `applyLog` isBigGang  
(True,"A freaking platoon.Compared gang size to 9")
```

跟之前的结果很像，只差在我们多了伴随产生的 log。再来多看几个例子：

```haskell
ghci> ("Tobin","Got outlaw name.") `applyLog` (\x -> (length x, "Applied length."))  
(5,"Got outlaw name.Applied length.")  
ghci> ("Bathcat","Got outlaw name.") `applyLog` (\x -> (length x, "Applied length"))  
(7,"Got outlaw name.Applied length")
```

可以看到在 lambda 里面 `x` 只是个正常的字串而不是 tuple，且 `applyLog` 帮我们处理掉附加 log 的动作。

### Monoids 的好处

```
请确定你了解什么是 Monoids。
```

到目前为止 `applyLog` 接受 `(a,String)` 型态的值，但为什么 log 一定要是 `String` 呢？我们使用 `++` 来附加新的 log，难道 `++` 并不能运作在任何形式的 list，而一定要限制我们在 `String` 上呢？我们当然可以摆脱 `String`，我们可以如下改变他的型态：

```haskell
applyLog :: (a,[c]) -> (a -> (b,[c])) -> (b,[c])
```

我们用一个 List 来代表 Log。包含在 List 中的元素型态必须跟原有的 List 跟回传的 List 型态相同，否则我们没办法用 `++` 来把他们接起来。

这能够运作在 bytestring 上吗？绝对没问题。只是我们现在的型态只对 List 有效。我们必须要另外做一个 bytestring 版本的 `applyLog`。但我们注意到 List 跟 bytestring 都是 monoids。因此他们都是 `Monoid` type class 的 instance，那代表他们都有实作 `mappend`。对 List 以及 bytestring 而言，`mappend` 都是拿来串接的。

```haskell
ghci> [1,2,3] `mappend` [4,5,6]  
[1,2,3,4,5,6]  
ghci> B.pack [99,104,105] `mappend` B.pack [104,117,97,104,117,97]  
Chunk "chi" (Chunk "huahua" Empty)
```

修改后我们的 `applyLog` 可以运作在任何 monoid 上。我们必须要修改型态宣告来表示这件事，同时也要在实作中把 `++` 改成 `mappend`：

```haskell
applyLog :: (Monoid m) => (a,m) -> (a -> (b,m)) -> (b,m)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log `mappend` newLog)
```

由于现在包含的值可以是任何 monoid，我们不再需要把 tuple 想成包含一个值跟对应的 log，我们可以想成他包含一个值跟一个对应的 monoid。举例来说，可以说我们有一个 tuple 包含一个产品名称跟一个符合 monoid 特性的产品价格。我们可以定义一个 `Sum` 的 newtype 来保证我们在操作产品的时候也会把价钱跟着加起来。

```haskell
import Data.Monoid  

type Food = String  
type Price = Sum Int  

addDrink :: Food -> (Food,Price)  
addDrink "beans" = ("milk", Sum 25)  
addDrink "jerky" = ("whiskey", Sum 99)  
addDrink _ = ("beer", Sum 30)
```

我们用 string 来代表食物，用 `newtype` 重新定义 `nInt` 为 `Sum`，来追踪总共需要花多少钱。可以注意到我们用 `mappend` 来操作 `Sum` 的时候，价钱会被一起加起来。

```haskell
ghci> Sum 3 `mappend` Sum 9  
Sum {getSum = 12}
```

`addDrink` 的实作很简单，如果我们想吃豆子，他会回传 `"milk"` 以及伴随的 `Sum 25`，同样的如果我们要吃 "jerky"，他就会回传 "whiskey"，要吃其他东西的话，就会回传 "beer"。乍看之下这个函数没什么特别，但如果用 `applyLog` 的话就会有趣些。

```haskell
ghci> ("beans", Sum 10) `applyLog` addDrink  
("milk",Sum {getSum = 35})  
ghci> ("jerky", Sum 25) `applyLog` addDrink  
("whiskey",Sum {getSum = 124})  
ghci> ("dogmeat", Sum 5) `applyLog` addDrink  
("beer",Sum {getSum = 35})
```

牛奶价值 `25` 美分，但如果我们也吃了价值 `10` 美分的豆子的话，总共需要付 `35` 美分。这样很清楚地展示了伴随的值不一定需要是 log，他可以是任何 monoid。至于两个值要如何结合，那要看 monoid 中怎么定义。当我们需要的是 log 的时候，他们是串接，但这个 case 里面，数字是被加起来。

由于 `addDrink` 回传一个 `(Food,Price)`，我们可以再把结果重新喂给 `addDrink`，这可以很容易告诉我们总共喝了多少钱：

```haskell
ghci> ("dogmeat", Sum 5) `applyLog` addDrink `applyLog` addDrink  
("beer",Sum {getSum = 65})
```

将狗食跟 30 美分的啤酒加在一起会得到 `("beer", Sum 35)`。如果我们用 `applyLog` 将上面的结果再喂给 `addDrink`，我们会得到 `("beer", Sum 65)` 这样的结果。

### The Writer type

我们认识了一个附加 monoid 的值其实表现出来的是一个 monad，我们来再来看看其他类似的 `Monad` instance。`Control.Monad.Writer` 这模块含有 `Writer w a` 的一个型态，里面定义了他 `Monad` 的 instance，还有一些操作这些值的函数。

首先，我们来看一下型态。要把一个 monoid 附加给一个值，只需要定义一个 tuple 就好了。`Writer w a` 这型态其实是一个 `newtype` wrapper。他的定义很简单：

```haskell
newtype Writer w a = Writer { runWriter :: (a, w) }
```

他包在一个 `newtype` 里面，并且可以是一个 `Monad` 的 instance，而且这样定义的好处是可以跟单纯 tuple 的型态区分开来。`a` 这个型态参数代表是包含的值的型态，而 `w` 则是附加的 monoid 的型态。

他 `Monad` instance 的定义如下：

```haskell
instance (Monoid w) => Monad (Writer w) where  
    return x = Writer (x, mempty)  
    (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
```

![](/files/-MlVFmY-4QUsHhh65qCr)

首先，我们来看看 `>>=`。他的实作基本上就是 `applyLog`，只是我们的 tuple 现在是包在一个 `Writer` 的 `newtype` 中，我们可以用 pattern matching 的方式把他给 unwrap。我们将 `x` 喂给 `f`。这会回给我们 `Writer w a`。接着可以用 `let` expression 来做 pattern matching。把结果绑定到 `y` 这个名字上，然后用 `mappend` 来结合旧的 monoid 值跟新的 monoid 值。最后把结果跟 monoid 值用 `Writer` constructor 包起来，形成我们最后的 `Writer` value。

那 `return` 呢？回想 `return` 的作用是接受一个值，并回传一个具有意义的最小 context 来装我们的值。那究竟什么样的 context 可以代表我们的 `Writer` 呢？如果我们希望 monoid 值所造成的影响愈小愈好，那 `mempty` 是个合理的选择。`mempty` 是被当作 identity monoid value，像是 `""` 或 `Sum 0`，或是空的 bytestring。当我们对 `mempty` 用 `mappend` 跟其他 monoid 值结合，结果会是其他的 monoid 值。所以如果我们用 `return` 来做一个 `Writer`，然后用 `>>=` 来喂给其他的函数，那函数回传的便是算出来的 monoid。下面我们试着用 `return` 搭配不同 context 来回传 `3`：

```haskell
ghci> runWriter (return 3 :: Writer String Int)  
(3,"")  
ghci> runWriter (return 3 :: Writer (Sum Int) Int)  
(3,Sum {getSum = 0})  
ghci> runWriter (return 3 :: Writer (Product Int) Int)  
(3,Product {getProduct = 1})
```

因为 `Writer` 并没有定义成 `Show` 的 instance，我们必须用 `runWriter` 来把我们的 `Writer` 转成正常的 tuple。对于 `String`，monoid 的值就是空字串。而对于 `Sum` 来说则是 `0`，因为 `0` 加上其他任何值都会是对方。而对 `Product` 来说，则是 `1`。

这里的 `Writer` instance 并没有定义 `fail`，所以如果 pattern matching 失败的话，就会调用 `error`。

### Using do notation with Writer

既然我们定义了 `Monad` 的 instance，我们自然可以用 `do` 串接 `Writer` 型态的值。这在我们需要对一群 `Writer` 型态的值做处理时显得特别方便。就如其他的 monad，我们可以把他们当作具有 context 的值。在现在这个 case 中，所有的 monoid 的值都会用 `mappend` 来连接起来并得到最后的结果。这边有一个简单的范例，我们用 `Writer` 来相乘两个数。

```haskell
import Control.Monad.Writer  

logNumber :: Int -> Writer [String] Int  
logNumber x = Writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b)
```

`logNumber` 接受一个数并把这个数做成一个 `Writer`。我们再用一串 string 来当作我们的 monoid 值，每一个数都跟着一个只有一个元素的 list，说明我们只有一个数。`multWithLog` 式一个 `Writer`，他将 `3` 跟 `5` 相乘并确保相乘的纪录有写进最后的 log 中。我们用 `return` 来做成 `a*b` 的结果。我们知道 `return` 会接受某个值并加上某个最小的 context，我们可以确定他不会多添加额外的 log。如果我们执行程序会得到：

```haskell
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5"])
```

有时候我们就是想要在某个时间点放进某个 Monoid value。`tell` 正是我们需要的函数。他实作了 `MonadWriter` 这个 type class，而且在当 `Writer` 用的时候也能接受一个 monoid value，好比说 `["This is going on"]`。我们能用他来把我们的 monoid value 接到任何一个 dummy value `()` 上来形成一个 Writer。当我们拿到的结果是 `()` 的时候，我们不会把他绑定到变量上。来看一个 `multWithLog` 的范例：

```haskell
multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    tell ["Gonna multiply these two"]  
    return (a*b)
```

`return (a*b)` 是我们的最后一行，还记得在一个 `do` 中的最后一行代表整个 `do` 的结果。如果我们把 `tell` 摆到最后，则 `do` 的结果则会是 `()`。我们会因此丢掉乘法运算的结果。除此之外，log 的结果是不变的。

```haskell
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])
```

### Adding logging to programs

欧几里得算法是找出两个数的最大公因数。Haskell 已经提供了 `gcd` 的函数，但我们来实作一个具有 log 功能的 gcd：

```haskell
gcd' :: Int -> Int -> Int  
gcd' a b   
    | b == 0    = a  
    | otherwise = gcd' b (a `mod` b)
```

算法的内容很简单。首先他检查第二个数字是否为零。如果是零，那就回传第一个数字。如果不是，那结果就是第二个数字跟将第一个数字除以第二个数字的余数两个数字的最大公因数。举例来说，如果我们想知道 8 跟 3 的最大公因数，首先可以注意到 3 不是 0。所以我们要求的是 3 跟 2 的最大公因数(8 除以 3 余二)。接下去我可以看到 2 不是 0，所以我们要再找 2 跟 1 的最大公因数。同样的，第二个数不是 0，所以我们再找 1 跟 0 的最大公因数。最后第二个数终于是 0 了，所以我们得到最大公因数是 1。

```haskell
ghci> gcd' 8 3  
1
```

答案真的是这样。接着我们想加进 context，context 会是一个 monoid value 并且像是一个 log 一样。就像之前的范例，我们用一串 string 来当作我们的 monoid。所以 `gcd'` 会长成这样：

```haskell
gcd' :: Int -> Int -> Writer [String] Int
```

而他的代码会像这样：

```haskell
import Control.Monad.Writer  

gcd' :: Int -> Int -> Writer [String] Int  
gcd' a b  
  | b == 0 = do  
      tell ["Finished with " ++ show a]  
      return a  
  | otherwise = do  
      tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
      gcd' b (a `mod` b)
```

这个函数接受两个 `Int` 并回传一个 `Writer [String] Int`，也就是说是一个有 log context 的 `Int`。当 `b` 等于 `0` 的时候，我们用一个 `do` 来组成一个 `Writer` 的值。我们先用 `tell` 来写入我们的 log，然后用 `return` 来当作 `do` 的结果。当然我们也可以这样写：

```haskell
Writer (a, ["Finished with " ++ show a])
```

但我想 `do` 的表达方式是比较容易阅读的。接下来我们看看当 `b` 不等于 `0` 的时候。我们会把 `mod` 的使用情况写进 log。然后在 `do` 当中的第二行递归调用 `gcd'`。`gcd'` 现在是回传一个 `Writer` 的型态，所以 ``gcd' b (a `mod` b)`` 这样的写法是完全没问题的。

尽管去 trace 这个 `gcd'` 对于理解十分有帮助，但我想了解整个大概念，把值视为具有 context 是更加有用的。

接着来试试跑我们的 `gcd'`，他的结果会是 `Writer [String] Int`，如果我们把他从 `newtype` 中取出来，我们会拿到一个 tuple。tuple 的第一个部份就是我们要的结果：

```haskell
ghci> fst $ runWriter (gcd' 8 3)  
1
```

至于 log 呢，由于 log 是一连串 string，我们就用 `mapM_ putStrLn` 来把这些 string 印出来：

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter (gcd' 8 3)  
8 mod 3 = 2  
3 mod 2 = 1  
2 mod 1 = 0  
Finished with 1
```

把普通的算法转换成具有 log 是很棒的经验，我们不过是把普通的 value 重写成 Monadic value，剩下的就靠 `>>=` 跟 `Writer` 来帮我们处理一切。用这样的方法我们几乎可以对任何函数加上 logging 的功能。我们只要把普通的值换成 `Writer`，然后把一般的函数调用换成 `>>=` (当然也可以用 `do`)

### Inefficient list construction

当制作 `Writer` Monad 的时候，要特别注意你是使用哪种 monoid。使用 list 的话性能有时候是没办法接受的。因为 list 是使用 `++` 来作为 `mappend` 的实现。而 `++` 在 list 很长的时候是非常慢的。

在之前的 `gcd'` 中，log 并不会慢是因为 list append 的动作实际上看起来是这样：

```haskell
a ++ (b ++ (c ++ (d ++ (e ++ f))))
```

list 是建立的方向是从左到右，当我们先建立左边的部份，而把另一串 list 加到右边的时候性能会不错。但如果我们不小心使用，而让 `Writer` monad 实际在操作 list 的时候变成像这样的话。

```haskell
((((a ++ b) ++ c) ++ d) ++ e) ++ f
```

这会让我们的操作是 left associative，而不是 right associative。这非常没有效率，因为每次都是把右边的部份加到左边的部份，而左边的部份又必须要从头开始建起。

下面这个函数跟 `gcd'` 差不多，只是 log 的顺序是相反的。他先纪录剩下的操作，然后纪录现在的步骤。

```haskell
import Control.Monad.Writer  

gcdReverse :: Int -> Int -> Writer [String] Int  
gcdReverse a b  
  | b == 0 = do  
      tell ["Finished with " ++ show a]  
      return a  
    | otherwise = do  
      result <- gcdReverse b (a `mod` b)  
      tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
      return result
```

他先递归调用，然后把结果绑定到 `result`。然后把目前的动作写到 log，在递归的结果之后。最后呈现的就是完整的 log。

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter (gcdReverse 8 3)  
Finished with 1  
2 mod 1 = 0  
3 mod 2 = 1  
8 mod 3 = 2
```

这没效率是因为他让 `++` 成为 left associative 而不是 right associative。

### Difference lists

![](/files/-MlVFmY5-JBbc7zuZ7dY)

由于 list 在重复 append 的时候显得低效，我们最好能使用一种支持高效 appending 的数据结构。其中一种就是 difference list。difference list 很类似 list，只是他是一个函数。他接受一个 list 并 prepend 另一串 list 到他前面。一个等价于 `[1,2,3]` 的 difference list 是这样一个函数 `\xs -> [1,2,3] ++ xs`。一个等价于 `[]` 的 difference list 则是 `\xs -> [] ++ xs`。

Difference list 最酷的地方在于他支持高效的 appending。当我们用 `++` 来实现 appending 的时候，他必须要走到左边的 list 的尾端，然后把右边的 list 一个个从这边接上。那 difference list 是怎么作的呢？appending 两个 difference list 就像这样

```haskell
f `append` g = \xs -> f (g xs)
```

`f` 跟 `g` 这边是两个函数，他们都接受一个 list 并 prepend 另一串 list。举例来说，如果 `f` 代表 `("dog"++)`（可以写成 `\xs -> "dog" ++ xs`）而 `g` 是 `("meat"++)`，那 ``f `append` g`` 就会做成一个新的函数，等价于：

```haskell
\xs -> "dog" ++ ("meat" ++ xs)
```

append 两个 difference list 其实就是用一个函数，这函数先喂一个 list 给第一个 difference list，然后再把结果喂给第二个 difference list。

我们可以用一个 `newtype` 来包起来

```haskell
newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
```

我们包起来的型态是 `[a] -> [a]`，因为 difference list 不过就是一个转换一个 list 到另一个 list 的函数。要把普通 list 转换成 difference list 也很容易。

```haskell
toDiffList :: [a] -> DiffList a  
toDiffList xs = DiffList (xs++)  

fromDiffList :: DiffList a -> [a]  
fromDiffList (DiffList f) = f []
```

要把一个普通 list 转成 difference list 不过就是照之前定义的，作一个 prepend 另一个 list 的函数。由于 difference list 只是一个 prepend 另一串 list 的一个函数，假如我们要转回来的话，只要喂给他空的 list 就行了。

这边我们给一个 difference list 的 `Monoid` 定义

```haskell
instance Monoid (DiffList a) where  
    mempty = DiffList (\xs -> [] ++ xs)  
    (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))
```

我们可以看到 `mempty` 不过就是 `id`，而 `mappend` 其实是 function composition。

```haskell
ghci> fromDiffList (toDiffList [1,2,3,4] `mappend` toDiffList [1,2,3])  
[1,2,3,4,1,2,3]
```

现在我们可以用 difference list 来加速我们的 `gcdReverse`

```haskell
import Control.Monad.Writer  

gcd' :: Int -> Int -> Writer (DiffList String) Int  
gcd' a b  
  | b == 0 = do  
      tell (toDiffList ["Finished with " ++ show a])  
      return a  
  | otherwise = do  
      result <- gcd' b (a `mod` b)  
      tell (toDiffList [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)])  
      return result
```

我们只要把 monoid 的型态从 `[String]` 改成 `DiffList String`，并在使用 `tell` 的时候把普通的 list 用 `toDiffList` 转成 difference list 就可以了。

```haskell
ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ gcdReverse 110 34  
Finished with 2  
8 mod 2 = 0  
34 mod 8 = 2  
110 mod 34 = 8
```

我们用 `runWriter` 来取出 `gcdReverse 110 34` 的结果，然后用 `snd` 取出 log，并用 `fromDiffList` 转回普通的 list 印出来。

### Comparing Performance

要体会 Difference List 能如何增进效率，考虑一个从某数数到零的 case。我们纪录的时候就像 `gcdReverse` 一样是反过来记的，所以在 log 中实际上是从零数到某个数。

```haskell
finalCountDown :: Int -> Writer (DiffList String) ()  
finalCountDown 0 = do  
    tell (toDiffList ["0"])  
finalCountDown x = do  
    finalCountDown (x-1)  
    tell (toDiffList [show x])
```

如果我们喂 `0`，他就只 log 0。如果喂其他正整数，他会先倒数到 `0` 然后 append 那些数到 log 中，所以如果我们调用 `finalCountDown` 并喂给他 `100`，那 log 的最后一笔就会是 `"100"`。

如果你把这个函数 load 进 GHCi 中并喂给他一个比较大的整数 `500000`，你会看到他无停滞地从 `0` 开始数起：

```haskell
ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ finalCountDown 500000  
0  
1  
2
```

但如果我们用普通的 list 而不用 difference list

```haskell
finalCountDown :: Int -> Writer [String] ()  
finalCountDown 0 = do  
    tell ["0"]  
finalCountDown x = do  
    finalCountDown (x-1)  
    tell [show x]
```

并下同样的指令

```haskell
ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000
```

我们会看到整个运算卡卡的。

当然这不是一个严谨的测试方法，但足以表显出 difference list 是比较有效率的写法。

## Reader Monad

![](/files/-MlVFmY93xqIeXsp812v)

在讲 Applicative 的章节中，我们说过了 `(->) r` 的型态只是 `Functor` 的一个 instance。要将一个函数 `f` map over 一个函数 `g`，基本上等价于一个函数，他可以接受原本 `g` 接受的参数，先套用 `g` 然后再把其结果丢给 `f`。

```haskell
ghci> let f = (*5)  
ghci> let g = (+3)
ghci> (fmap f g) 8
```

我们已经见识过函数当作 applicative functors 的例子。这样能让我们对函数的结果直接进行操作。

```haskell
ghci> let f = (+) <$> (*2) <*> (+10)
ghci> f 3
19
```

`(+) <$> (*2) <*> (+10)` 代表一个函数，他接受一个数值，分别把这数值交给 `(*2)` 跟 `(+10)`。然后把结果加起来。例如说，如果我们喂 `3` 给这个函数，他会分别对 `3` 做 `(*2)` 跟 `(+10)` 的动作。而得到 `6` 跟 `13`。然后调用 `(+)`，而得到 `19`。

其实 `(->) r` 不只是一个 functor 跟一个 applicative functor，他也是一个 monad。就如其他 monadic value 一般，一个函数也可以被想做是包含一个 context 的。这个 context 是说我们期待某个值，他还没出现，但我们知道我们会把他当作函数的参数，调用函数来得到结果。

我们已经见识到函数是怎样可以看作 functor 或是 applicative functors 了。再来让我们看看当作 `Monad` 的一个 instance 时会是什么样子。你可以在 `Control.Monad.Instances` 里面找到，他看起来像这样：

```haskell
instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w
```

我们之前已经看过函数的 `pure` 实作了，而 `return` 差不多就是 `pure`。他接受一个值并把他放进一个 minimal context 里面。而要让一个函数能够是某个定值的唯一方法就是让他完全忽略他的参数。

而 `>>=` 的实作看起来有点难以理解，我们可以仔细来看看。当我们使用 `>>=` 的时候，喂进去的是一个 monadic value，处理他的是一个函数，而吐出来的也是一个 monadic value。在这个情况下，当我们将一个函数喂进一个函数，吐出来的也是一个函数。这就是为什么我们在最外层使用了一个 lambda。在我们目前看过的实作中，`>>=` 几乎都是用 lambda 将内部跟外部隔开来，然后在内部来使用 `f`。这边也是一样的道理。要从一个函数得到一个结果，我们必须喂给他一些东西，这也是为什么我们先用 `(h w)` 取得结果，然后将他丢给 `f`。而 `f` 回传一个 monadic value，在这边这个 monadic value 也就是一个函数。我们再把 `w` 喂给他。

如果你还不太懂 `>>=` 怎么写出来的，不要担心，因为接下来的范例会让你晓得这真的是一个简单的 Monad。我们造一个 `do` expression 来使用这个 Monad。

```
import Control.Monad.Instances  

addStuff :: Int -> Int  
addStuff = do  
  a <- (*2)  
  b <- (+10)  
  return (a+b)
```

这跟我们之前写的 applicative expression 差不多，只差在他是运作在 monad 上。一个 `do` expression 的结果永远会是一个 monadic vlaue，这个也不例外。而这个 monadic value 其实是一个函数。只是在这边他接受一个数字，然后套用 `(*2)`，把结果绑定到 `a` 上面。而 `(+10)` 也同用被套用到同样的参数。结果被绑定到 `b` 上。`return` 就如其他 monad 一样，只是制作一个简单的 monadic value 而不会作多余的事情。这让整个函数的结果是 `a+b`。如果我们试着跑跑看，会得到之前的结果。

```haskell
ghci> addStuff 3  
19
```

其中 `3` 会被喂给 `(*2)` 跟 `(+10)`。而且他也会被喂给 `return (a+b)`，只是他会忽略掉 `3` 而永远回传 `a+b` 正因为如此，function monad 也被称作 reader monad。所有函数都从一个固定的地方读取。要写得更清楚一些，可以把 `addStuff` 改写如下：

```haskell
addStuff :: Int -> Int  
addStuff x = let  
    a = (*2) x  
    b = (+10) x  
    in a+b
```

我们见识了把函数视作具有 context 的值很自然的可以表达成 reader monad。只要我们当作我们知道函数会回传什么值就好。他作的就是把所有的函数都黏在一起做成一个大的函数，然后把这个函数的参数都喂给全部组成的函数，这有点取出他们未来的值的意味。实作做完了然后 `>>=` 就会保证一切都能正常运作。

## State Monad

![](/files/-MlVFmYCvUx3G8mhmwec)

Haskell 是一个纯粹的语言，正因为如此，我们的程序是有一堆没办法改变全域状态或变量的函数所组成，他们只会作些处理并回传结果。这样的性质让我们很容易思考我们的程序在干嘛，因为我们不需要担心变量在某一个时间点的值是什么。然而，有一些领域的问题根本上就是依赖于随着时间而改变的状态。虽然我们也可以用 Haskell 写出这样的程序，但有时候写起来蛮痛苦的。这也是为什么 Haskell 要加进 State Monad 这个特性。这让我们在 Haskell 中可以容易地处理状态性的问题，并让其他部份的程序还是保持纯粹性。

当我们处理乱数的时候，我们的函数接受一个 random generator 并回传一个新的乱数跟一个新的 random generator。如果我们需要很多个乱数，我们可以用前一个函数回传的 random generator 继续做下去。当我们要写一个接受 `StdGen` 的函数并产生丢三个硬币结果的函数，我们会这样写：

```haskell
threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let (firstCoin, newGen) = random gen  
        (secondCoin, newGen') = random newGen  
        (thirdCoin, newGen''') = random newGen'  
    in  (firstCoin, secondCoin, thirdCoin)
```

他接受一个 `gen` 然后用 `random gen` 产生一个 `Bool` 型态的值以及新的 generator。要仿真丢第二个硬币的话，便使用新的 generator。在其他语言中，多半除了乱数之外不需要多回传一个 generator。那是因为我们可以对现有的进行修改。但 Haskell 是纯粹的语言，我们没办法那么做，所以我们必须要接受一个状态，产生结果然后回传一个新的状态，然后用新的状态来继续做下去。

一般来讲你应该不会喜欢这么写，在程序中有赤裸裸的状态，但我们又不想放弃 Haskell 的纯粹性质。这就是 State Monad 的好处了，他可以帮我们处理这些琐碎的事情，又让我们保持 Haskell 的纯粹性。

为了深入理解状态性的计算，我们先来看看应该给他们什么样的型态。我们会说一个状态性的计算是一个函数，他接受一个状态，回传一个值跟一个新的状态。写起来会像这样：

```haskell
s -> (a,s)
```

`s` 是状态的型态，而 `a` 是计算结果的型态。

```
在其他的语言中，赋值大多是被当作会改变状态的操作。举例来说，当我们在命令式语言写 ``x = 5``，这通常代表的是把 ``5`` 指定给 ``x`` 这变量。而且这边 ``5`` 是一个 expression。

如果你用函数语言的角度去思考，你可以把他想做是一个函数，接受一个状态，并回传结果跟新的状态。那新的状态代表所有已指定的值与新加入的变量。
```

这种改变状态的计算，除了想做是一个接受状态并回传结果跟新状态的函数外，也可以想做是具有 context 的值。 实际的值是结果。然而要得到结果，我们必须要给一个初始的状态，才能得到结果跟最后的状态。

### Stack and Stones

考虑现在我们要对一个堆叠的操作建立模型。你可以把东西推上堆叠顶端，或是把东西从顶端拿下来。如果你要的元素是在堆叠的底层的话，你必须要把他上面的东西都拿下来才能拿到他。

我们用一个 list 来代表我们的堆叠。而我们把 list 的头当作堆叠的顶端。为了正确的建立模型，我们要写两个函数：`pop` 跟 `push`。`pop` 会接受一个堆叠，取下一个元素并回传一个新的堆叠，这个新的堆叠不包含取下的元素。`push` 会接受一个元素，把他堆到堆叠中，并回传一个新的堆叠，其包含这个新的元素。

```haskell
type Stack = [Int]  

pop :: Stack -> (Int,Stack)  
pop (x:xs) = (x,xs)  

push :: Int -> Stack -> ((),Stack)  
push a xs = ((),a:xs)
```

我们用 `()` 来当作 pushing 的结果，毕竟推上堆叠并不需要什么回传值，他的重点是在改变堆叠。注意到 `push` 跟 `pop` 都是改变状态的计算，可以从他们的型态看出来。

我们来写一段程序来仿真一个堆叠的操作。我们接受一个堆叠，把 `3` 推上去，然后取出两个元素。

```haskell
stackManip :: Stack -> (Int, Stack)  
stackManip stack = let  
    ((),newStack1) = push 3 stack  
    (a ,newStack2) = pop newStack1  
    in pop newStack2
```

我们拿一个 `stack` 来作 `push 3 stack` 的动作，其结果是一个 tuple。tuple 的第一个部份是 `()`，而第二个部份是新的堆叠，我们把他命名成 `newStack1`。然后我们从 `newStack1` 上 pop 出一个数字。其结果是我们之前 push 上去的一个数字 `a`，然后把这个更新的堆叠叫做 `newStack2`。然后我们从 `newStack2` 上再 pop 出一个数字 `b`，并得到 `newStack3`。我们回传一个 tuple 跟最终的堆叠。

```haskell
ghci> stackManip [5,8,2,1]  
(5,[8,2,1])
```

结果就是 `5` 跟新的堆叠 `[8,2,1]`。注意到 `stackManip` 是一个会改变状态的操作。我们把一堆会改变状态的操作绑在一起操作，有没有觉得很耳熟的感觉。

`stackManip` 的程序有点冗长，因为我们要写得太详细，必须把状态给每个操作，然后把新的状态再喂给下一个。如果我们可以不要这样作的话，那程序应该会长得像这样：

```haskell
stackManip = do  
    push 3  
    a <- pop  
    pop
```

这就是 State Monad 在做的事。有了他，我们便可以免除于要把状态操作写得太明白的窘境。

### The State Monad

`Control.Monad.State` 这个模块提供了一个 `newtype` 包起来的型态。

```haskell
newtype State s a = State { runState :: s -> (a,s) }
```

一个 `State s a` 代表的是一个改变状态的操作，他操纵的状态为型态 `s`，而产生的结果是 `a`。

我们已经见识过什么是改变状态的操作，以及他们是可以被看成具有 context 的值。接着来看看他们 `Monad` 的 instance：

```haskell
instance Monad (State s) where  
    return x = State $ \s -> (x,s)  
    (State h) >>= f = State $ \s -> let (a, newState) = h s  
                                        (State g) = f a  
                                    in  g newState
```

我们先来看看 `return` 那一行。我们 `return` 要作的事是接受一个值，并做出一个改变状态的操作，让他永远回传那个值。所以我们才做了一个 lambda 函数，`\s -> (x,s)`。我们把 `x` 当成是结果，并且状态仍然是 `s`。这就是 `return` 要完成的 minimal context。

![](/files/-MlVFmYEVStd444X6ybJ)

那 `>>=` 的实作呢？很明显的把改变状态的操作喂进 `>>=` 也必须要丢出另一个改变状态的操作。所以我们用 `State` 这个 `newtype` wrapper 来把一个 lambda 函数包住。这个 lambda 会是新的一个改变状态的操作。但里面的内容是什么？首先我们应该要从接受的操作取出结果。由于 lambda 是在一个大的操作中，所以我们可以喂给 `h` 我们现在的状态，也就是 `s`。那会产生 `(a, newState)`。到目前为止每次我们在实作 `>>=` 的时候，我们都会先从 monadic value 中取出结果，然后喂给 `f` 来得到新的 monadic value。在写 `Writer` 的时候，我们除了这样作还要确保 context 是用 `mappend` 把旧的 monoid value 跟新的接起来。在这边我们则是用 `f a` 得到一个新的操作 `g`。现在我们有了新的操作跟新的状态（叫做 `newState`），我们就把 `newState` 喂给 `g`。结果便是一个 tuple，里面包含了最后的结果跟最终的状态。

有了 `>>=`，我们便可以把两个操作黏在一起，只是第二个被放在一个函数中，专门接受第一个的结果。由于 `pop` 跟 `push` 已经是改变状态的操作了，我们可以把他们包在 `State` 中

```haskell
import Control.Monad.State  

pop :: State Stack Int  
pop = State $ \(x:xs) -> (x,xs)  

push :: Int -> State Stack ()  
push a = State $ \xs -> ((),a:xs)
```

`pop` 已经满足我们的条件，而 `push` 要先接受一个 `Int` 才会回传我们要的操作。所以我们可以改写先前的范例如下：

```haskell
import Control.Monad.State  

stackManip :: State Stack Int  
stackManip = do  
  push 3  
  a <- pop  
  pop
```

看到我们是怎么把一个 `push` 跟两个 `pop` 黏成一个操作吗？当我们将他们从一个 `newtype` 取出，其实就是需要一个能喂进初始状态的函数：

```haskell
ghci> runState stackManip [5,8,2,1]  
(5,[8,2,1])
```

我们不须绑定第二个 `pop`，因为我们根本不会用到 `a`，所以可以写成下面的样子：

```haskell
stackManip :: State Stack Int  
stackManip = do  
    push 3  
    pop  
    pop
```

再来尝试另外一种方式，先从堆叠上取下一个数字，看看他是不是 `5`，如果是的话就把他放回堆叠上，如果不是的话就堆上 `3` 跟 `8`。

```haskell
stackStuff :: State Stack ()  
stackStuff = do  
    a <- pop  
    if a == 5  
        then push 5  
        else do  
            push 3  
            push 8
```

很直觉吧！我们来看看初始的堆叠的样子。

```haskell
ghci> runState stackStuff [9,0,2,1,0]  
((),[8,3,0,2,1,0])
```

还记得我们说过 `do` 的结果会是一个 monadic value，而在 `State` monad 的 case，`do` 也就是一个改变状态的函数。而由于 `stackManip` 跟 `stackStuff` 都是改变状态的计算，因此我们可以把他们黏在一起：

```haskell
moreStack :: State Stack ()  
moreStack = do  
    a <- stackManip  
    if a == 100  
        then stackStuff  
        else return ()
```

如果 `stackManip` 的结果是 `100`，我们就会跑 `stackStuff`，如果不是的话就什么都不做。`return ()` 不过就是什么是都不做，全部保持原样。

`Contorl.Monad.State` 提供了一个 `MonadState` 的 typeclass，他有两个有用的函数，分别是 `get` 跟 `put`。对于 `State` 来说，`get` 的实作就像这样：

```haskell
get = State $ \s -> (s,s)
```

他只是取出现在的状态除此之外什么也不做。而 `put` 函数会接受一个状态并取代掉现有的状态。

```haskell
put newState = State $ \s -> ((),newState)
```

有了这两个状态，我们便可以看到现在堆叠中有什么，或是把整个堆叠中的元素换掉。

```haskell
stackyStack :: State Stack ()  
stackyStack = do  
    stackNow <- get  
    if stackNow == [1,2,3]  
        then put [8,3,1]  
        else put [9,2,1]
```

我们可以看看对于 `State` 而言，`>>=` 的型态会是什么：

```haskell
(>>=) :: State s a -> (a -> State s b) -> State s b
```

我们可以看到状态的型态都是 `s`，而结果从型态 `a` 变成型态 `b`。这代表我们可以把好几个改变状态的计算黏在一起，这些计算的结果可以都不一样，但状态的型态会是一样的。举例来说，对于 `Maybe` 而言，`>>=` 的型态会是：

```haskell
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
```

`Maybe` 不变是有道理的，但如果用 `>>=` 来把两种不同的 monad 接起来是没道理的。但对于 state monad 而言，monad 其实是 `State s`，所以如果 `s` 不一样，我们就要用 `>>=` 来把两个 monad 接起来。

### 随机性与 state monad

在章节的一开始，我们知道了在 Haskell 中要产生乱数的不方便。我们要拿一个产生器，并回传一个乱数跟一个新的产生器。接下来我们还一定要用新的产生器不可。但 State Monad 让我们可以方便一些。

`System.Random` 中的 `random` 函数有下列的型态：

```haskell
random :: (RandomGen g, Random a) => g -> (a, g)
```

代表他接受一个乱数产生器，并产生一个乱数跟一个新的产生器。很明显他是一个会改变状态的计算，所以我们可以用 `newtype` 把他包在一个 `State` 中，然后把他当作 monadic value 来操作。

```haskell
import System.Random  
import Control.Monad.State  

randomSt :: (RandomGen g, Random a) => State g a  
randomSt = State random
```

这样我们要丢三个硬币的结果可以改写成这样：

```haskell
import System.Random  
import Control.Monad.State  

threeCoins :: State StdGen (Bool,Bool,Bool)  
threeCoins = do  
  a <- randomSt  
  b <- randomSt  
  c <- randomSt  
  return (a,b,c)
```

`threeCoins` 是一个改变状态的计算，他接受一个初始的乱数产生器，他会把他喂给 `randomSt`，他会产生一个数字跟一个新的产生器，然后会一直传递下去。我们用 `return (a,b,c)` 来呈现 `(a,b,c)`，这样并不会改变最近一个产生器的状态。

```haskell
ghci> runState threeCoins (mkStdGen 33)  
((True,False,True),680029187 2103410263)
```

要完成像这样要改变状态的任务便因此变得轻松了很多。

## Error Monad

我们知道 `Maybe` 是拿来赋予一个值具有可能失败的 context。一个值可能会是 `Just something` 或是一个 `Nothing`。尽管这很有用，但当我们拿到了一个 `Nothing`，我们只知道他失败了，但我们没办法塞进一些有用的信息，告诉我们究竟是在什么样的情况下失败了。

而 `Either e a` 则能让我们可以加入一个可能会发生错误的 context，还可以增加些有用的消息，这样能让我们知道究竟是什么东西出错了。一个 `Either e a` 的值可以是代表正确的 `Right`，或是代表错误的 `Left`，例如说：

```haskell
ghci> :t Right 4  
Right 4 :: (Num t) => Either a t  
ghci> :t Left "out of cheese error"  
Left "out of cheese error" :: Either [Char] b
```

这就像是加强版的 `Maybe`，他看起来实在很像一个 monad，毕竟他也可以当作是一个可能会发生错误的 context，只是多了些消息罢了。

在 `Control.Monad.Error` 里面有他的 `Monad` instance。

```haskell
instance (Error e) => Monad (Either e) where  
    return x = Right x   
    Right x >>= f = f x  
    Left err >>= f = Left err  
    fail msg = Left (strMsg msg)
```

`return` 就是建立起一个最小的 context，由于我们用 `Right` 代表正确的结果，所以他把值包在一个 `Right` constructor 里面。就像实作 `Maybe` 时的 `return` 一样。

`>>=` 会检查两种可能的情况：也就是 `Left` 跟 `Right`。如果进来的是 `Right`，那我们就调用 `f`，就像我们在写 `Just` 的时候一样，只是调用对应的函数。而在错误的情况下，`Left` 会被传出来，而且里面保有描述失败的值。

`Either e` 的 `Monad` instance 有一项额外的要求，就是包在 `Left` 中的型态，也就是 `e`，必须是 `Error` typeclass 的 instance。`Error` 这个 typeclass 描述一个可以被当作错误消息的型态。他定义了 `strMsg` 这个函数，他接受一个用字串表达的错误。一个明显的范例就是 `String` 型态，当他是 `String` 的时候，`strMsg` 只不过回传他接受到的字串。

```haskell
ghci> :t strMsg  
strMsg :: (Error a) => String -> a  
ghci> strMsg "boom!" :: String  
"boom!"
```

但因为我们通常在用 `Either` 来描述错误的时候，是用 `String` 来装错误消息，所以我们也不用担心这一点。当在 `do` 里面做 pattern match 失败的时候，`Left` 的值会拿来代表失败。

总之来看看一个范例吧：

```haskell
ghci> Left "boom" >>= \x -> return (x+1)  
Left "boom"  
ghci> Right 100 >>= \x -> Left "no way!"  
Left "no way!"
```

当我们用 `>>=` 来把一个 `Left` 喂进一个函数，函数的运算会被忽略而直接回传丢进去的 `Left` 值。当我们喂 `Right` 值给函数，函数就会被计算而得到结果，但函数还是产生了一个 `Left` 值。

当我们试着喂一个 `Right` 值给函数，而且函数也成功地计算，我们却碰到了一个奇怪的 type error。

```haskell
ghci> Right 3 >>= \x -> return (x + 100)  

<interactive>:1:0:  
  Ambiguous type variable `a' in the constraints:  
    `Error a' arising from a use of `it' at <interactive>:1:0-33  
    `Show a' arising from a use of `print' at <interactive>:1:0-33  
  Probable fix: add a type signature that fixes these type variable(s)
```

Haskell 警告说他不知道要为 `e` 选择什么样的型态，尽管我们是要印出 `Right` 的值。这是因为 `Error e` 被限制成 `Monad`。把 `Either` 当作 Monad 使用就会碰到这样的错误，你只要明确写出 type signature 就行了：

```haskell
ghci> Right 3 >>= \x -> return (x + 100) :: Either String Int  
Right 103
```

这样就没问题了。

撇除这个小毛病，把 `Either` 当 Monad 使用就像使用 `Maybe` 一样。在前一章中，我们展示了 `Maybe` 的使用方式。你可以把前一章的范例用 `Either` 重写当作练习。

## 一些实用的 Moandic functions

在这个章节，我们会看看一些操作 monadic value 的函数。这样的函数通常我们称呼他们为 monadic function。其中有些你是第一次见到，但有些不过是 `filter` 或 `foldl` 的变形。让我们来看看吧！

### liftM

![](/files/-MlVFmYLb3XUhh9HgRL9)

当我们开始学习 Monad 的时候，我们是先学习 functors，他代表可以被 map over 的事物。接着我们学了 functors 的加强版，也就是 applicative functors，他可以对 applicative values 做函数的套用，也可以把一个一般值放到一个缺省的 context 中。最后，我们介绍在 applicative functors 上更进一步的 monad，他让这些具有 context 的值可以被喂进一般函数中。

也就是说每一个 monad 都是个 applicative functor，而每一个 applicative functor 也都是一个 functor。`Applicative` typeclass 中有加入限制，让每一个 `Applicative` 都是 `Functor`。但 `Monad` 却没有这样的限制，让每个 `Monad` 都是 `Applicative`。这是因为 `Monad` 这个 typeclass 是在 `Applicative` 引入前就存在的缘故。

但即使每个 monad 都是一个 functor，但我们不需要依赖 `Functor` 的定义。那是因为我们有 `liftM` 这个函数。他会接受一个函数跟一个 monadic value，然后把函数 map over 那些 monadic value。所以他其实就是 `fmap`，以下是他的型态：

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b
```

而这是 `fmap` 的型态：

```haskell
fmap :: (Functor f) => (a -> b) -> f a -> f b
```

如果 `Functor` 跟 `Monad` 的 instance 遵守 functor 跟 monad 的法则（到目前为止我们看过的 monad 都遵守），那这两个函数其实是等价的。这就像 `pure` 跟 `return` 其实是同一件事，只是一个在 `Applicative` 中，而另外一个在 `Monad` 里面，我们来试试看 `liftM` 吧：

```haskell
ghci> liftM (*3) (Just 8)  
Just 24  
ghci> fmap (*3) (Just 8)  
Just 24  
ghci> runWriter $ liftM not $ Writer (True, "chickpeas")  
(False,"chickpeas")  
ghci> runWriter $ fmap not $ Writer (True, "chickpeas")  
(False,"chickpeas")  
ghci> runState (liftM (+100) pop) [1,2,3,4]  
(101,[2,3,4])  
ghci> runState (fmap (+100) pop) [1,2,3,4]  
(101,[2,3,4])
```

我们已经知道 `fmap` 是如何运作在 `Maybe` 上。而 `liftM` 又跟 `fmap` 等价。对于 `Writer` 型态的值而言，函数只有对他的第一个 component 做处理。而对于改变状态的计算，`fmap` 跟 `liftM` 也都是产生另一个改变状态的计算。我们也看过了 `(+100)` 当作用在 `pop` 上会产生 `(1, [2,3,4])`。

来看看 `liftM` 是如何被实作的：

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b  
liftM f m = m >>= (\x -> return (f x))
```

或者用 `do` 来表示得清楚些

```haskell
liftM :: (Monad m) => (a -> b) -> m a -> m b  
liftM f m = do  
    x <- m  
    return (f x)
```

我们喂一个 monadic value `m` 给函数，我们套用那个函数然后把结果放进一个缺省的 context。由于遵守 monad laws，这保证这操作不会改变 context，只会呈现最后的结果。我们可以看到实作中 `liftM` 也没有用到 `Functor` 的性质。这代表我们能只用 monad 提供给我们的就实作完 `fmap`。这特性让我们可以得到 monad 比 functor 性质要强的结论。

`Applicative` 让我们可以操作具有 context 的值就像操作一般的值一样。 就像这样：

```haskell
ghci> (+) <$> Just 3 <*> Just 5  
Just 8  
ghci> (+) <$> Just 3 <*> Nothing  
Nothing
```

使用 applicative 的特性让事情变得很精简。 `<$>` 不过就是 `fmap`，而 `<*>` 只是一个具有下列型态的函数：

```haskell
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
```

他有点像 `fmap`，只是函数本身有一个 context。我们必须把他从 context 中抽出，对 `f a` 做 map over 的东做，然后再放回 context 中。由于在 Haskel 中函数缺省都是 curried，我们便能用 `<$>` 以及 `<*>` 来让接受多个参数的函数也能接受 applicative 种类的值。

总之 `<*>` 跟 `fmap` 很类似，他也能只用 `Monad` 保证的性质实作出来。`ap` 这个函数基本上就是 `<*>`，只是他是限制在 `Monad` 上而不是 `Applicative` 上。这边是他的定义：

```haskell
ap :: (Monad m) => m (a -> b) -> m a -> m b  
ap mf m = do  
    f <- mf  
    x <- m  
    return (f x)
```

`mf` 是一个 monadic value，他的结果是一个函数。由于函数跟值都是放在 context 中，假设我们从 context 取出的函数叫 `f`，从 context 取出的值叫 `x`，我们把 `x` 喂给 `f` 然后再把结果放回 context。像这样：

```haskell
ghci> Just (+3) <*> Just 4  
Just 7  
ghci> Just (+3) `ap` Just 4  
Just 7  
ghci> [(+1),(+2),(+3)] <*> [10,11]  
[11,12,12,13,13,14]  
ghci> [(+1),(+2),(+3)] `ap` [10,11]  
[11,12,12,13,13,14]
```

由于我们能用 `Monad` 提供的函数实作出 `Applicative` 的函数，因此我们看到 Monad 有比 applicative 强的性质。事实上，当我们知道一个型态是 monad 的时候，大多数会先定义出 `Monad` 的 instance，然后才定义 `Applicative` 的 instance。而且只要把 `pure` 定义成 `return`，`<*>` 定义成 `ap` 就行了。同样的，如果你已经有了 `Monad` 的 instance，你也可以简单的定义出 `Functor`，只要把 `fmap` 定义成 `liftM` 就行了。

`liftA2` 是一个方便的函数，他可以把两个 applicative 的值喂给一个函数。他的定义很简单：

```haskell
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c  
liftA2 f x y = f <$> x <*> y
```

`liftM2` 也是做差不多的事情，只是多了 `Monad` 的限制。在函式库中其实也有 `liftM3`，`liftM4` 跟 `liftM5`。

我们看到了 monad 相较于 applicative 跟 functor 有比较强的性质。尽管 moand 有 functor 跟 applicative functor 的性质，但他们不见得有 `Functor` 跟 `Applicative` 的 instance 定义。所以我们查看了一些在 monad 中定义，且等价于 functor 或 applicative functor 所具有的函数。

### The join function

如果一个 monadic value 的结果是另一个 monadic value，也就是其中一个 monadic value 被包在另一个里面，你能够把他们变成一个普通的 monadic value 吗？就好像把他们打平一样。譬如说，我们有 `Just (Just 9)`，我们能够把他变成 `Just 9` 吗？事实上是可以的，这也是 monad 的一个性质。也就是我要看的 `join` 函数，他的型态是这样：

```haskell
join :: (Monad m) => m (m a) -> m a
```

他接受一个包在另一个 monadic value 中的 monadic value，然后会回给我们一个普通的 monadic value。这边有一些 `Maybe` 的范例：

```haskell
ghci> join (Just (Just 9))  
Just 9  
ghci> join (Just Nothing)  
Nothing  
ghci> join Nothing  
Nothing
```

第一行是一个计算成功的结果包在另一个计算成功的结果，他们应该要能结合成为一个比较大的计算成功的结果。第二行则是一个 `Nothing` 包在一个 `Just` 中。我们之前在处理 `Maybe` 型态的值时，会用 `<*>` 或 `>>=` 把他们结合起来。输入必须都是 `Just` 时结果出来才会是 `Just`。如果中间有任何的失败，结果就会是一个失败的结果。而第三行就是这样，我们尝试把失败的结果接合起来，结果也会是一个失败。

要 `join` 一个 list 也是很简单：

```haskell
ghci> join [[1,2,3],[4,5,6]]  
[1,2,3,4,5,6]
```

你可以看到，对于 list 而言 `join` 不过就是 `concat`。 而要 `join` 一个包在 `Writer` 中的 `Writer`， 我们必须用 `mappend`：

```haskell
ghci> runWriter $ join (Writer (Writer (1,"aaa"),"bbb"))  
(1,"bbbaaa")
```

`"bbb"` 先被加到 monoid 中，接着 `"aaa"` 被附加上去。你想要查看 `Writer` 中的值的话，必须先把值写进去才行。

要对 `Either` 做 `join` 跟对 `Maybe` 做 `join` 是很类似的：

```haskell
ghci> join (Right (Right 9)) :: Either String Int  
Right 9  
ghci> join (Right (Left "error")) :: Either String Int  
Left "error"  
ghci> join (Left "error") :: Either String Int  
Left "error"
```

如果我们对一个包了另外一个改变状态的计算的进行改变状态的计算，要作 `join` 的动作会让外面的先被计算，然后才是计算里面的：

```haskell
ghci> runState (join (State $ \s -> (push 10,1:2:s))) [0,0,0]  
((),[10,1,2,0,0,0])
```

这边的 lambda 函数接受一个状态，并把 `2` 跟 `1` 放到堆叠中，并把 `push 10` 当作他的结果。当对整个东西做 `join` 的时候，他会先把 `2` 跟 `1` 放到堆叠上，然后进行 `push 10` 的计算，因而把 `10` 放到堆叠的顶端。

`join` 的实作像是这样：

```haskell
join :: (Monad m) => m (m a) -> m a  
join mm = do  
    m <- mm  
    m
```

因为 `mm` 的结果会是一个 monadic value，我们单独用 `m <- mm` 拿取他的结果。这也可以说明 `Maybe` 只有当外层跟内层的值都是 `Just` 的时候才会是 `Just`。如果把 `mm` 的值设成 `Just (Just 8)` 的话，他看起来会是这样：

```haskell
joinedMaybes :: Maybe Int  
joinedMaybes = do  
    m <- Just (Just 8)  
    m
```

![](/files/-MlVFmY_QDMkZBvmEpyd)

最有趣的是对于一个 monadic value 而言，用 `>>=` 把他喂进一个函数其实等价于对 monad 做 mapping over 的动作，然后用 `join` 来把值从 nested 的状态变成扁平的状态。也就是说 `m >>= f` 其实就是 `join (fmap f m)`。如果你仔细想想的话其实很明显。`>>=` 的使用方式是，把一个 monadic value 喂进一个接受普通值的函数，但他却会回传 monadic value。如果我们 map over 一个 monadic value，我们会做成一个 monadic value 包了另外一个 monadic value。例如说，我们现在手上有 `Just 9` 跟 `\x -> Just (x+1)`。如果我们把这个函数 map over `Just 9`，我们会得到 `Just (Just 10)`

事实上 `m >>= f` 永远等价于 `join (fmap f m)` 这性质非常有用。如果我们要定义自己的 `Monad` instance，要知道怎么把 nested monadic value 变成扁平比起要定义 `>>=` 是比较容易的一件事。

### filterM

`filter` 函数是 Haskell 中不可或缺的要素。他接受一个断言(predicate)跟一个 list 来过滤掉断言为否的部份并回传一个新的 list。他的型态是这样：

```haskell
filter :: (a -> Bool) -> [a] -> [a]
```

predicate 能接 list 中的一个元素并回传一个 `Bool` 型态的值。但如果 `Bool` 型态其实是一个 monadic value 呢？也就是他有一个 context。例如说除了 `True` 跟 `False` 之外还伴随一个 monoid，像是 `["Accepted the number 5"]`，或 `["3 is too small"]`。照前面所学的听起来是没问题，而且产出的 list 也会跟随 context，在这个例子中就是 log。所以如果 `Bool` 会回传伴随 context 的布尔值，我们会认为最终的结果也会具有 context。要不然这些 context 都会在处理过程中遗失。

在 `Control.Monad` 中的 `filterM` 函数正是我们所需要的，他的型态如下：

```haskell
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
```

predicate 会回传一个 monadic value，他的结果会是 `Bool` 型态，由于他是 monadic value，他的 context 有可能会是任何 context，譬如说可能的失败，non-determinism，甚至其他的 context。一旦我们能保证 context 也会被保存在最后的结果中，结果也就是一个 monadic value。

我们来写一个接受 list 然后过滤掉小于 4 的函数。先尝试使用 `filter` 函数：

```haskell
ghci> filter (\x -> x < 4) [9,1,5,2,10,3]  
[1,2,3]
```

很简单吧。接着我们在做个 predicate，除了表达 `True` 或 `False` 之外，还提供了一个 log。我们会用 `Writer` monad 来表达这件事：

```haskell
keepSmall :: Int -> Writer [String] Bool  
keepSmall x  
    | x < 4 = do  
        tell ["Keeping " ++ show x]  
        return True  
    | otherwise = do  
        tell [show x ++ " is too large, throwing it away"]  
        return False
```

这个函数会回传 `Writer [String] Bool` 而不是一个单纯的 `Bool`。他是一个 monadic predicate。如果扫到的数字小于 `4` 的话，我们就会回报要保存他，而且回传 `return True`。

接着，我们把他跟一个 list 喂给 `filterM`。由于 predicate 会回传 `Writer`，所以结果仍会是一个 `Writer` 值。

```haskell
ghci> fst $ runWriter $ filterM keepSmall [9,1,5,2,10,3]  
[1,2,3]
```

要检查 `Writer` 的结果，我们想要印出 log 看看里面有什么东西：

```haskell
ghci> mapM_ putStrLn $ snd $ runWriter $ filterM keepSmall [9,1,5,2,10,3]  
9 is too large, throwing it away  
Keeping 1  
5 is too large, throwing it away  
Keeping 2  
10 is too large, throwing it away  
Keeping 3
```

提供 monadic predicate 给 `filterM`，我们便能够做 filter 的动作，同时还能保有 monadic context。

一个比较炫的技巧是用 `filterM` 来产生一个 list 的 powerset。一个 powerset 就是一个集合所有子集所形成的集合。如果说我们的 list 是 `[1,2,3]`，那他个 powerset 就会是：

```haskell
[1,2,3]  
[1,2]  
[1,3]  
[1]  
[2,3]  
[2]  
[3]  
[]
```

换句话说，要产生一个 powerset 就是要列出所有要丢掉跟保留的组合。`[2,3]` 只不过代表我们把 `1` 给丢掉而已。

我们要依赖 non-determinism 来写我们这产生 powerset 的函数。我们接受一个 list `[1,2,3]` 然后查看第一个元素，这个例子中是 `1`，我们会问：我们要保留他呢？还是丢掉他呢？答案是我们都要做。所以我们会用一个 non-determinism 的 predicate 来过滤我的 list。也就是我们的 `powerset` 函数：

```haskell
powerset :: [a] -> [[a]]  
powerset xs = filterM (\x -> [True, False]) xs
```

等等，我们已经写完了吗？没错，就这么简单，我们可以同时丢掉跟保留每个元素。只要我们用 non-deterministic predicate，那结果也就是一个 non-deterministic value，也便是一个 list 的 list。试着跑跑看：

```haskell
ghci> powerset [1,2,3]  
[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]
```

这样的写法需要让你好好想一下，但如果你能接受 list 其实就是 non-deterministic value 的话，那要想通会比较容易一些。

### foldM

`foldl` 的 monadic 的版本叫做 `foldM`。如果你还有印象的话，`foldl` 会接受一个 binary 函数，一个起始累加值跟一串 list，他会从左边开始用 binary 函数每次带进一个值来 fold。`foldM` 也是做同样的事，只是他接受的这个 binary 函数会产生 monadic value。不意外的，他的结果也会是 monadic value。`foldl` 的型态是：

```haskell
foldl :: (a -> b -> a) -> a -> [b] -> a
```

而 `foldM` 的型态则是：

```haskell
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
```

binary 函数的回传值是 monadic，所以结果也会是 monadic。我们来试着把 list 的值用 fold 全部加起来：

```haskell
ghci> foldl (\acc x -> acc + x) 0 [2,8,3,1]  
14
```

这边起始的累加值是 `0`，首先 `2` 会被加进去，变成 `2`。然后 `8` 被加进去变成 `10`，直到我们没有值可以再加，那便是最终的结果。

但如果我们想额外加一个条件，也就是当碰到一个数字大于 `9` 时候，整个运算就算失败呢？一种合理的修改就是用一个 binary 函数，他会检查现在这个数是否大于 `9`，如果是便引发失败，如果不是就继续。由于有失败的可能性，我们便需要这个 binary 函数回传一个 `Maybe`，而不是一个普通的值。我们来看看这个函数：

```haskell
binSmalls :: Int -> Int -> Maybe Int  
binSmalls acc x  
    | x > 9     = Nothing  
    | otherwise = Just (acc + x)
```

由于这边的 binary 函数是 monadic function，我们不能用普通的 `foldl`，我们必须用 `foldM`：

```haskell
ghci> foldM binSmalls 0 [2,8,3,1]  
Just 14  
ghci> foldM binSmalls 0 [2,11,3,1]  
Nothing
```

由于这串 list 中有一个数值大于 `9`，所以整个结果会是 `Nothing`。另外你也可以尝试 fold 一个回传 `Writer` 的 binary 函数，他会在 fold 的过程中纪录你想纪录的信息。

### Making a safe RPN calculator

![](/files/-LjUms-ziISQJ2u8MPUQ)

之前的章节我们实作了一个 RPN 计算机，但我们没有做错误的处理。他只有在输入是合法的时候才会运算正确。假如有东西出错了，整个程序便会当掉。我们在这章看到了要怎样把代码转换成 monadic 的版本，我们先尝适用 `Maybe` monad 来帮我们的 RPN 计算机加上些错误处理。

我们的 RPN 计算机接受一个像 `"1 3 + 2 *"` 这样的字串，把他断成 word，变成 `["1","3","+","2","*"]` 这样。然后用一个 binary 函数，跟一个空的堆叠，从左边开始或是将数值推进堆叠中，或是操作堆叠最上层的两个元素。

以下便是程序的核心部份：

```haskell
import Data.List  

solveRPN :: String -> Double  
solveRPN = head . foldl foldingFunction [] . words
```

我们把输入变成一个字串的 list，从左边开始 fold，当堆叠中只剩下一个元素的时候，他便是我们要的答案。以下是我们的 folding 函数：

```haskell
foldingFunction :: [Double] -> String -> [Double]  
foldingFunction (x:y:ys) "*" = (x * y):ys  
foldingFunction (x:y:ys) "+" = (x + y):ys  
foldingFunction (x:y:ys) "-" = (y - x):ys  
foldingFunction xs numberString = read numberString:xs
```

这边我们的累加元素是一个堆叠，我们用一个 `Double` 的 list 来表示他。当我们在做 folding 的过程，如果当前的元素是一个 operator，他会从堆叠上拿下两个元素，用 operator 施行运算然后把结果放回堆叠。如果当前的元素是一个表示成字串的数字，他会把字串转换成数字，并回传一个新的堆叠包含了转换后的数字。

我们首先把我们的 folding 函数加上处理错误的 case，所以他的型态会变成这样：

```haskell
foldingFunction :: [Double] -> String -> Maybe [Double]
```

他不是回传一个 `Just` 的堆叠就是回传 `Nothing`。

`reads` 函数就像 `read` 一样，差别在于他回传一个 list。在成功读取的情况下 list 中只包含读取的那个元素。如果他失败了，他会回传一个空的 list。除了回传读取的元素，他也回传剩下读取失败的元素。他必须要看完整串输入，我们想把他弄成一个 `readMaybe` 的函数，好方便我们进行。

```haskell
readMaybe :: (Read a) => String -> Maybe a  
readMaybe st = case reads st of [(x,"")] -> Just x  
                                _ -> Nothing
```

测试结果如下：

```haskell
ghci> readMaybe "1" :: Maybe Int  
Just 1  
ghci> readMaybe "GO TO HELL" :: Maybe Int  
Nothing
```

看起来运作正常。我们再把他变成一个可以处理失败情况的 monadic 函数

```haskell
foldingFunction :: [Double] -> String -> Maybe [Double]  
foldingFunction (x:y:ys) "*" = return ((x * y):ys)  
foldingFunction (x:y:ys) "+" = return ((x + y):ys)  
foldingFunction (x:y:ys) "-" = return ((y - x):ys)  
foldingFunction xs numberString = liftM (:xs) (readMaybe numberString)
```

前三种 case 跟前面的很像，只差在堆叠现在是包在 `Just` 里面（我们常常是用 `return` 来做到这件事，但其实我们也可以用 `Just`）。在最后一种情况，我们用 `readMaybe numberString` 然后我们用 `(:xs)` map over 他。所以如果堆叠 `xs` 是 `[1.0,2.0]` 且 `readMaybe numberString` 产生 `Just 3.0`，那结果便是 `Just [3.0,1.0,2.0]`。如果 `readyMaybe numberString` 产生 `Nothing` 那结果便是 `Nothing`。我们来试着跑跑看 folding 函数

```haskell
ghci> foldingFunction [3,2] "*"  
Just [6.0]  
ghci> foldingFunction [3,2] "-"  
Just [-1.0]  
ghci> foldingFunction [] "*"  
Nothing  
ghci> foldingFunction [] "1"  
Just [1.0]  
ghci> foldingFunction [] "1 wawawawa"  
Nothing
```

看起来正常运作。我们可以用他来写一个新的 `solveRPN`。

```haskell
import Data.List  

solveRPN :: String -> Maybe Double  
solveRPN st = do  
  [result] <- foldM foldingFunction [] (words st)  
  return result
```

我们仍是接受一个字串把他断成一串 word。然后我们用一个空的堆叠来作 folding 的动作，只差在我们用的是 `foldM` 而不是 `foldl`。`foldM` 的结果会是 `Maybe`，`Maybe` 里面包含了一个只有一个元素的 list。我们用 `do` expression 来取出值，把他绑定到 `result` 上。当 `foldM` 回传 `Nothing` 的时候，整个结果就变成 `Nothing`。也特别注意我们有在 `do` 里面做 pattern match 的动作，所以如果 list 中不是只有一个元素的话，最后结果便会是 `Nothing`。最后一行我们用 `return result` 来展示 RPN 计算的结果，把他包在一个 `Maybe` 里面。

```haskell
ghci> solveRPN "1 2 * 4 +"  
Just 6.0  
ghci> solveRPN "1 2 * 4 + 5 *"  
Just 30.0  
ghci> solveRPN "1 2 * 4"  
Nothing  
ghci> solveRPN "1 8 wharglbllargh"  
Nothing
```

第一个例子会失败是因为 list 中不是只有一个元素，所以 `do` 里面的 pattern matching 失败了。第二个例子会失败是因为 `readMaybe` 回传了 `Nothing`。

### Composing monadic functions

当我们介绍 monad law 的时候，我们说过 `<=<` 就像是函数合成一样，只差在一个是作用在普通函数 `a -> b`。一个是作用在 monadic 函数 `a -> m b`。

```haskell
ghci> let f = (+1) . (*100)  
ghci> f 4  
401  
ghci> let g = (\x -> return (x+1)) <=< (\x -> return (x*100))  
ghci> Just 4 >>= g  
Just 401
```

在这个例子中我们合成了两个普通的函数，并喂给给他 `4`。我们也合成了两个 monadic 函数并用 `>>=` 喂给他 `Just 4`。

如果我们在 list 中有一大堆函数，我们可以把他们合成一个巨大的函数。用 `id` 当作累加的起点，`.` 当作 binary 函数，用 fold 来作这件事。

```haskell
ghci> let f = foldr (.) id [(+1),(*100),(+1)]  
ghci> f 1  
201
```

`f` 接受一个数字，然后会帮他加 `1`，乘以 `100`，再加 `1`。我们也可以将 monadic 函数用同样的方式做合成，只是不用 `.` 而用 `<=<`，不用 `id` 而用 `return`。我们不需要 `foldM`，由于 `<=<` 只用 `foldr` 就足够了。

当我们在之前的章节介绍 list monad 的时候， 我们用他来解决一个骑士是否能在三步内走到另一点的问题。 那个函数叫做 `moveKnight`， 他接受一个座标然后回传所有可能的下一步。 然后产生出所有可能三步的移动。

```haskell
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
```

要检查我们是否能只用三步从 `start` 走到 `end`，我们用下列函数

```haskell
canReachIn3 :: KnightPos -> KnightPos -> Bool  
canReachIn3 start end = end `elem` in3 start
```

如果使用 monadic 版本的合成的话，我们也可以做一个类似的 `in3`，但我们希望他不只有三步的版本，而希望有任意步的版本。如果你仔细观察 `in3`，他只不过用 `>>=` 跟 `moveKnight` 把之前所有可能结果喂到下一步。把他一般化，就会像下面的样子：

```haskell
import Data.List  

inMany :: Int -> KnightPos -> [KnightPos]  
inMany x start = return start >>= foldr (<=<) return (replicate x moveKnight)
```

首先我们用 `replicate` 来做出一个 list，里面有 `x` 份的 `moveKnight`。然后我们把所有函数都合成起来，就会给我们从起点走 `x` 步内所有可能的的位置。然后我们只需要把起始位置喂给他就好了。

我们也可以一般化我们的 `canReachIn3`：

```haskell
canReachIn :: Int -> KnightPos -> KnightPos -> Bool  
canReachIn x start end = end `elem` inMany x start
```

## 定义自己的 Monad

![](/files/-MlVFmYjtWaaeebK7oOF)

在这一章节，我们会带你看看究竟一个型态是怎么被辨认，确认是一个 monad 而且正确定义出 `Monad` 的 instance。我们通常不会为了定义 monad 而定义。比较常发生的是，我们想要针对一个问题建立模型，却稍后发现我们定义的型态其实是一个 Monad，所以就定义一个 `Monad` 的 instance。

正如我们看到的，list 是被拿来当作 non-deterministic values。对于 `[3,5,9]`，我们可以看作是一个 non-deterministic value，我们不能知道究竟是哪一个。当我们把一个 list 用 `>>=` 喂给一个函数，他就是把一串可能的选择都丢给函数，函数一个个去计算在那种情况下的结果，结果也便是一个 list。

如果我们把 `[3,5,9]` 看作是 `3`,`5`,`9` 各出现一次，但这边没有每一种数字出现的机率。如果我们把 non-deterministic 的值看作是 `[3,5,9]`，但 `3` 出现的机率是 50%，`5` 跟 `9` 出现的机率各是 25%呢？我们来试着用 Haskell 描述看看。

如果说 list 中的每一个元素都伴随着他出现的机率。那下面的形式就蛮合理的：

```haskell
[(3,0.5),(5,0.25),(9,0.25)]
```

在数学上，机率通常不是用百分比表示，而是用介于 0 跟 1 的实数表示。0 代表不可能会发生，而 1 代表绝对会发生。但浮点数很有可能很快随着运算失去精准度，所以 Haskell 有提供有理数。他的型态是摆在 `Data.Ratio` 中，叫做 `Rational`。要创造出一个 `Rational`，我们会把他写成一个分数的形式。分子跟分母用 `%` 分隔。这边有几个例子：

```haskell
ghci> 1%4  
1 % 4  
ghci> 1%2 + 1%2  
1 % 1  
ghci> 1%3 + 5%4  
19 % 12
```

第一行代表四分之一，第二行代表两个二分之一加起来变成一。而第三行我们把三分之一跟四分之五加起来变成十二分之十九。所以我们来用 `Rational` 取代浮点数来当作我们的机率值吧。

```haskell
ghci> [(3,1%2),(5,1%4),(9,1%4)]  
[(3,1 % 2),(5,1 % 4),(9,1 % 4)]
```

所以 `3` 有二分之一的机会出现，而 `5` 跟 `9` 有四分之一的机会出现。

可以看到我们帮 list 加上了一些额外的 context。再我们继续深入之前，我们用一个 `newtype` 把他包起来，好让我们帮他写 instance。

```haskell
import Data.Ratio

newtype Prob a = Prob { getProb :: [(a,Rational)] } deriving Show
```

接着我们想问，这是一个 functor 吗？list 是一个 functor，所以很有可能他也是一个 functor，毕竟我们只是在 list 上多加一些东西而已。在 list 的情况下，我们可以针对每个元素用函数做处理。这边我们也是用函数针对每个元素做处理，只是我们是输出机率值。所以我们就来写个 functor 的 instance 吧。

```haskell
instance Functor Prob where  
    fmap f (Prob xs) = Prob $ map (\(x,p) -> (f x,p)) xs
```

我们可以用 pattern matching 的方式把 `newtype` 解开来，套用函数 `f` 之后再包回去。过程中不会动到机率值。

```haskell
ghci> fmap negate (Prob [(3,1%2),(5,1%4),(9,1%4)])  
Prob {getProb = [(-3,1 % 2),(-5,1 % 4),(-9,1 % 4)]}
```

要注意机率的和永远是 `1`。如果我们没有漏掉某种情形的话，没有道理他们加起来的值不为 `1`。一个有 75% 机率是正面以及 50% 机率是反面的硬币根本没什么道理。

接着要问一个重要的问题，他是一个 monad 吗？我们知道 list 是一个 monad，所以他很有可能也是一个 monad。首先来想想 `return`。他在 list 是怎么运作的？他接受一个普通的值并把他放到一个 list 中变成只有一个元素的 list。那在这边又如何？由于他是一个最小的 context，他也应该是一个元素的 list。那机率值呢？`return x` 的值永远都是 `x`，所以机率值不应该是 `0`，而应该是 `1`。

至于 `>>=` 呢？看起来有点复杂，所以我们换种方式来思考，我们知道 `m >>= f` 会等价于 `join (fmap f m)`，所以我们来想要怎么把一串包含 probability list 的 list 弄平。举个例子，考虑一个 list，`'a'` 跟 `'b'` 恰出现其中一个的机率为 25%，两个出现的机率相等。而 `'c'` 跟 `'d'` 恰出现其中一个的机率为 75%，两个出现的机率也是相等。这边有一个图将情形画出来。

![](/files/-MlVFmYltyI0sgV7jzAF)

每个字母发生的机率有多高呢？如果我们用四个盒子来代表每个字母，那每个盒子的机率为何？每个盒子的机率是他们所装有的机率值相乘的结果。`'a'` 的机率是八分之一，`'b'` 同样也是八分之一。八分之一是因为我们把二分之一跟四分之一相乘得到的结果。而 `'c'` 发生的机率是八分之三，是因为二分之一乘上四分之三。`'d'` 同样也是八分之三。如果把所有的机率加起来，就会得到一，符合机率的规则。

来看看怎么用一个 list 表达我们要说明的东西：

```haskell
thisSituation :: Prob (Prob Char)  
thisSituation = Prob  
    [( Prob [('a',1%2),('b',1%2)] , 1%4 )  
    ,( Prob [('c',1%2),('d',1%2)] , 3%4 )  
    ]
```

注意到这边的型态是 `Prob (Prob Char)`。所以我们要思考的是如何把一串包含机率 list 的 list 打平。如果能成功写出这样的逻辑，`>>=` 不过就是 `join (fmap f m)`，我们便得到了一个 monad。我们这边写了一个 `flatten` 来做这件事。

```haskell
flatten :: Prob (Prob a) -> Prob a  
flatten (Prob xs) = Prob $ concat $ map multAll xs  
    where multAll (Prob innerxs,p) = map (\(x,r) -> (x,p*r)) innerxs
```

`multAll` 接受一个 tuple，里面包含一个 probability list 跟一个伴随的机率值 `p`，所以我们要作的事是把 list 里面的机率值都乘以 `p`，并回传一个新的 tuple 包含新的 list 跟新的机率值。我们将 `multAll` map over 到我们的 probability list 上，我们就成功地打平了我们的 list。

现在我们就能定义我们的 `Monad` instance。

```haskell
instance Monad Prob where  
    return x = Prob [(x,1%1)]  
    m >>= f = flatten (fmap f m)  
    fail _ = Prob []
```

![](/files/-MlVFmYnf66HLPmdC6jS)

由于我们已经把所有苦工的做完了，定义这个 instance 显得格外轻松。我们也定义了 `fail`，我们定义他的方式跟定义 list 一样。如果在 `do` 中发生了失败的 pattern match，那就会调用 `fail`。

检查我们定义的 instance 是否遵守 monad law 也是很重要的。monad law 的第一个定律是 `return x >>= f` 应该要等价于 `f x`。要写出严格的证明会很麻烦，但我们可以观察到下列事实：首先用 `return` 做一个最小的 context，然后用 `fmap` 将一个函数 map over 这个 context，再将他打平。这样做出来的 probability list，每一个机率值都相当于将我们最初放到 minimal context 中的值乘上 `1%1`。同样的逻辑，也可以看出 `m >>= return` 是等价于 `m`。第三个定律是 `f <=< (g <=< h)` 应该要等价于 `(f <=< g) <=< h`。我们可以从乘法有结合律的性质，以及 list monad 的特性上推出 probability monad 也符合这个定律。`1%2 * (1%3 * 1%5)` 等于 `(1%2 * 1%3) * 1%5`。

现在我们有了一个 monad，这样有什么好处呢？他可以帮助我们计算机率值。我们可以把机率事件看作是具有 context 的 value，而 probability monad 可以保证机率值能正确地被计算成最终的结果。

好比说我们现在有两个普通的硬币以及一个灌铅的硬币。灌铅的硬币十次中有九次会出现正面，只有一次会出现反面。如果我们一次丢掷这三个硬币，有多大的机会他们都会出现正面呢？让我们先来表达丢掷硬币这件事，分别丢的是灌铅的跟普通的硬币。

```haskell
data Coin = Heads | Tails deriving (Show, Eq)  

coin :: Prob Coin  
coin = Prob [(Heads,1%2),(Tails,1%2)]  

loadedCoin :: Prob Coin  
loadedCoin = Prob [(Heads,1%10),(Tails,9%10)]
```

最后，来看看掷硬币的函数：

```haskell
import Data.List (all)  

flipThree :: Prob Bool  
flipThree = do  
  a <- coin  
  b <- coin  
  c <- loadedCoin  
  return (all (==Tails) [a,b,c])
```

试着跑一下的话，我们会看到尽管我们用了不公平的硬币，三个反面的机率还是不高。

```haskell
ghci> getProb flipThree  
[(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),  
 (False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]
```

同时出现正面的机率是四十分之九，差不多是 25%的机会。我们的 monad 并没有办法 join 所有都是 `False` 的情形，也就是所有硬币都是出现反面的情况。不过那不是个严重的问题，可以写个函数来将同样的结果变成一种结果，这就留给读者当作习题。

在这章节中，我们从提出问题到真的写出型态，并确认这个型态是一个 monad，写出他的 instance 并实际操作他。这是个很棒的经验。现在读者们应该对于 monad 有不少的了解才是。


# ch14


# Zippers 数据结构

![](/files/-LjUmqZQt63a-vEeRQAH)

尽管 Haskell 的纯粹性质带来很多好处，但他让一些在非纯粹语言很容易处理的一些事情变得要用另一种方法解决。由于 referential transparency，同样一件事在 Haskell 中是没有分别的。所以如果我们有一个装满 5 的树，而我们希望把其中一个换成 6，那我们必须要知道我们究竟是想改变哪个 5。我们也必须知道我们身处在这棵树的哪里。但在 Haskell 中，每个 5 都长得一样，我们并不能因为他们在内存中的地址不同就把他们区分开来。我们也不能改变任何状态，当我们想要改变一棵树的时候，我们实际上是说我们要一棵新的树，只是他长得很像旧的。一种解决方式是记住一条从根节点到现在这个节点的路径。我们可以这样表达：给定一棵树，先往左走，再往右走，再往左走，然后改变你走到的元素。虽然这是可行的，但这非常没有效率。如果我们想接连改变一个在附近的节点，我们必须再从根节点走一次。在这个章节中，我们会看到我们可以集中注意在某个数据结构上，这样让改变数据结构跟遍历的动作非常有效率。

## 来走二元树吧!

我们在生物课中学过，树有非常多种。所以我们来自己发明棵树吧！

```haskell
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
```

这边我们的树不是空的就是有两棵子树。来看看一个范例：

```haskell
freeTree :: Tree Char  
freeTree =   
    Node 'P'  
        (Node 'O'  
             (Node 'L'  
              (Node 'N' Empty Empty)  
              (Node 'T' Empty Empty)  
             )  
             (Node 'Y'  
              (Node 'S' Empty Empty)  
              (Node 'A' Empty Empty)  
             )  
        )  
        (Node 'L'  
             (Node 'W'  
                  (Node 'C' Empty Empty)  
                  (Node 'R' Empty Empty)  
             )  
             (Node 'A'  
                  (Node 'A' Empty Empty)  
                  (Node 'C' Empty Empty)  
             )  
        )
```

画成图的话就是像这样：

![](/files/-LjUmqZSecT-78Nz73x2)

注意到 `W` 这个节点了吗？如果我们想要把他变成 `P`。我们会怎么做呢？一种方式是用 pattern match 的方式做，直到我们找到那个节点为止。要先往右走再往左走，再改变元素内容，像是这样：

```haskell
changeToP :: Tree Char -> Tree Char  
changeToP (Node x l (Node y (Node _ m n) r)) = Node x l (Node y (Node 'P' m n) r)
```

这不只看起来很丑，而且很不容易阅读。这到底是怎么回事？我们使用 pattern match 来拆开我们的树，我们把 root 绑定成 `x`，把左子树绑定成 `l`。对于右子树我们继续使用 pattern match。直到我们碰到一个子树他的 root 是 `'W'`。到此为止我们再重建整棵树，新的树只差在把 `'W'` 改成了 `'P'`。

有没有比较好的作法呢？有一种作法是我们写一个函数，他接受一个树跟一串 list，里面包含有行走整个树时的方向。方向可以是 `L` 或是 `R`，分别代表向左走或向右走。我们只要跟随指令就可以走达指定的位置：

```haskell
data Direction = L | R deriving (Show)  
type Directions = [Direction]  

changeToP :: Directions-> Tree Char -> Tree Char  
changeToP (L:ds) (Node x l r) = Node x (changeToP ds l) r  
changeToP (R:ds) (Node x l r) = Node x l (changeToP ds r)  
changeToP [] (Node _ l r) = Node 'P' l r
```

如果在 list 中的第一个元素是 `L`，我们会建构一个左子树变成 `'P'` 的新树。当我们递归地调用 `changeToP`，我们只会传给他剩下的部份，因为前面的部份已经看过了。对于 `R` 的 case 也一样。如果 list 已经消耗完了，那表示我们已经走到我们的目的地，所以我们就回传一个新的树，他的 root 被修改成 `'P'`。

要避免印出整棵树，我们要写一个函数告诉我们目的地究竟是什么元素。

```haskell
elemAt :: Directions -> Tree a -> a  
elemAt (L:ds) (Node _ l _) = elemAt ds l  
elemAt (R:ds) (Node _ _ r) = elemAt ds r  
elemAt [] (Node x _ _) = x
```

这函数跟 `changeToP` 很像，只是他不会记下沿路上的信息，他只会记住目的地是什么。我们把 `'W'` 变成 `'P'`，然后用他来查看。

```haskell
ghci> let newTree = changeToP [R,L] freeTree  
ghci> elemAt [R,L] newTree  
'P'
```

看起来运作正常。在这些函数里面，包含方向的 list 比较像是一种"focus"，因为他特别指出了一棵子树。一个像 `[R]` 这样的 list 是聚焦在 root 的右子树。一个空的 list 代表的是主树本身。

这个技巧看起来酷炫，但却不太有效率，特别是在我们想要重复地改变内容的时候。假如我们有一个非常大的树以及非常长的一串包含方向的 list。我们需要沿着方向从 root 一直走到树的底部。如果我们想要改变一个邻近的元素，我们仍需要从 root 开始走到树的底部。这实在不太令人满意。

在下一个章节，我们会介绍一个比较好的方法，让我们可以有效率地改变我们的 focus。

## 凡走过必留下痕迹

![](/files/-LjUmqZU3ccfbSGJ84rh)

我们需要一个比包含一串方向的 list 更好的聚焦的方法。如果我们能够在从 root 走到指定地点的沿路上撒下些面包屑，来纪录我们的足迹呢？当我们往左走，我们便记住我们选择了左边，当我们往右走，便记住我们选择了右边。

要找个东西来代表我们的面包屑，就用一串 `Direction` (他可以是 `L` 或者是 `R`)，只是我们叫他 `BreadCrumb` 而不叫 `Direction`。这是因为现在我们把这串 direction 反过来看了：

```haskell
type Breadcrumbs = [Direction]
```

这边有一个函数，他接受一棵树跟一些面包屑，并在我们往左走时在 list 的前头加上 `L`

```haskell
goLeft :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs)
goLeft (Node _ l _, bs) = (l, L:bs)
```

我们忽略 root 跟右子树，直接回传左子树以及面包屑，只是在现有的面包屑前面加上 `L`。再来看看往右走的函数：

```haskell
goRight :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs)  
goRight (Node _ _ r, bs) = (r, R:bs)
```

几乎是一模一样。我们再来做一个先往右走再往左走的函数，让他来走我们的 `freeTree`

```haskell
ghci> goLeft (goRight (freeTree, []))  
(Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R])
```

![](/files/-LjUmqZWmjdjNXT4ZZmE)

现在我们有了一棵树，他的 root 是 `'W'`，而他的左子树的 root 是 `'C'`，右子树的 root 是 `'R'`。而由于我们先往右走再往左走，所以面包屑是 `[L,R]`。

要再表示得更清楚些，我们能用定义一个 `-:`

```haskell
x -: f = f x
```

他让我们可以将值喂给函数这件事反过来写，先写值，再来是 `-:`，最后是函数。所以我们可以写成 `(freeTree, []) -: goRight` 而不是 `goRight (freeTree, [])`。我们便可以把上面的例子改写地更清楚。

```haskell
ghci> (freeTree, []) -: goRight -: goLeft  
(Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R])
```

### Going back up

如果我们想要往回上走回我们原来的路径呢？根据留下的面包屑，我们知道现在的树是他父亲的左子树，而他的父亲是祖父的右子树。这些信息并不足够我们往回走。看起来要达到这件事情，我们除了单纯纪录方向之外，还必须把其他的数据都记录下来。在这个案例中，也就是他的父亲以及他的右子树。

一般来说，单单一个面包屑有足够的信息让我们重建父亲的节点。所以他应该要包含所有我们没有选择的路径的信息，并且他应该要纪录我们沿路走的方向。同时他不应该包含我们现在锁定的子树。因为那棵子树已经在 tuple 的第一个部份中，如果我们也把他纪录在面包屑里，那就会有重复的信息。

我们来修改一下我们面包屑的定义，让他包含我们之前丢掉的信息。我们定义一个新的型态，而不用 `Direction`：

```haskell
data Crumb a = LeftCrumb a (Tree a) | RightCrumb a (Tree a) deriving (Show)
```

我们用 `LeftCrumb` 来包含我们没有走的右子树，而不仅仅只写个 `L`。我们用 `RightCrumb` 来包含我们没有走的左子树，而不仅仅只写个 `R`。

这些面包屑包含了所有重建树所需要的信息。他们像是软碟一样存了许多我们的足迹，而不仅仅只是方向而已。

大致上可以把每个面包屑想像成一个树的节点，树的节点有一个洞。当我们往树的更深层走，面包屑携带有我们所有走过得所有信息，只除了目前我们锁定的子树。他也必须纪录洞在哪里。在 `LeftCrumb` 的案例中，我们知道我们是向左走，所以我们缺少的便是左子树。

我们也要把 `Breadcrumbs` 的 type synonym 改掉：

```haskell
type Breadcrumbs a = [Crumb a]
```

接着我们修改 `goLeft` 跟 `goRight` 来纪录一些我们没走过的路径的信息。不像我们之前选择忽略他。`goLeft` 像是这样：

```haskell
goLeft :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)
goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
```

你可以看到跟之前版本的 `goLeft` 很像，不只是将 `L` 推到 list 的最前端，我们还加入 `LeftCrumb` 来表示我们选择向左走。而且我们在 `LeftCrumb` 里面塞有我们之前走的节点，以及我们选择不走的右子树的信息。

要注意这个函数会假设我们锁定的子树并不是 `Empty`。一个空的树并没有任何子树，所以如果我们选择在一个空的树中向左走，就会因为我们对 `Node` 做模式匹配而产生错误。我们没有处理 `Empty` 的情况。

`goRight` 也是类似：

```haskell
goRight :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)  
goRight (Node x l r, bs) = (r, RightCrumb x l:bs)
```

在之前我们只能向左或向右走，现在我们由于纪录了关于父节点的信息以及我们选择不走的路的信息，而获得向上走的能力。来看看 `goUp` 函数：

```haskell
goUp :: (Tree a, Breadcrumbs a) -> (Tree a, Breadcrumbs a)  
goUp (t, LeftCrumb x r:bs) = (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
```

![](/files/-LjUmqZYe3G_5swBwCIj)

我们锁定了 `t` 这棵树并检查最新的 `Crumb`。如果他是 `LeftCrumb`，那我们就建立一棵新的树，其中 `t` 是他的左子树并用关于我们没走过得右子树的信息来填写其他 `Node` 的信息。由于我们使用了面包屑的信息来建立父子树，所以新的 list 移除了我们的面包屑。

如果我们已经在树的顶端并使用这个函数的话，他会引发错误。等一会我们会用 `Maybe` 来表达可能失败的情况。

有了 `Tree a` 跟 `Breadcrumbs a`，我们就有足够的信息来重建整棵树，并且锁定其中一棵子树。这种方式让我们可以轻松的往上，往左，往右走。这样成对的数据结构我们叫做 Zipper，因为当我们改变锁定的时候，他表现得很像是拉链一样。所以我们便定义一个 type synonym:

```haskell
type Zipper a = (Tree a, Breadcrumbs a)
```

我个人是比较倾向于命名成 `Focus`，这样可以清楚强调我们是锁定在其中一部分， 至于 Zipper 被更广泛地使用，所以这边仍维持叫他做 `Zipper`。

### Manipulating trees under focus

现在我们具备了移动的能力，我们再来写一个改变元素的函数，他能改变我们目前锁定的子树的 root。

```haskell
modify :: (a -> a) -> Zipper a -> Zipper a  
modify f (Node x l r, bs) = (Node (f x) l r, bs)  
modify f (Empty, bs) = (Empty, bs)
```

如果我们锁定一个节点，我们用 `f` 改变他的 root。如果我们锁定一棵空的树，那就什么也不做。我们可以移来移去并走到我们想要改变的节点，改变元素后并锁定在那个节点，之后我们可以很方便的移上移下。

```haskell
ghci> let newFocus = modify (\_ -> 'P') (goRight (goLeft (freeTree,[])))
```

我们往左走，然后往右走并将 root 取代为 `'P'`，用 `-:` 来表达的话就是：

```haskell
ghci> let newFocus = (freeTree,[]) -: goLeft -: goRight -: modify (\_ -> 'P')
```

我们也能往上走并置换节点为 `'X'`：

```haskell
ghci> let newFocus2 = modify (\_ -> 'X') (goUp newFocus)
```

如果我们用 `-:` 表达的话：

```haskell
ghci> let newFocus2 = newFocus -: goUp -: modify (\_ -> 'X')
```

往上走很简单，毕竟面包屑中含有我们没走过的路径的信息，只是里面的信息是相反的，这有点像是要把袜子反过来才能用一样。有了这些信息，我们就不用再从 root 开始走一遍，我们只要把反过来的树翻过来就好，然后锁定他。

每个节点有两棵子树，即使子树是空的也是视作有树。所以如果我们锁定的是一棵空的子树我们可以做的事就是把他变成非空的，也就是叶节点。

```haskell
attach :: Tree a -> Zipper a -> Zipper a  
attach t (_, bs) = (t, bs)
```

我们接受一棵树跟一个 zipper，回传一个新的 zipper，锁定的目标被换成了提供的树。我们不只可以用这招把空的树换成新的树，我们也能把现有的子树给换掉。让我们来用一棵树换掉我们 `freeTree` 的最左边：

```haskell
ghci> let farLeft = (freeTree,[]) -: goLeft -: goLeft -: goLeft -: goLeft  
ghci> let newFocus = farLeft -: attach (Node 'Z' Empty Empty)
```

`newFocus` 现在锁定在我们刚刚接上的树上，剩下部份的信息都放在面包屑里。如果我们用 `goUp` 走到树的最上层，就会得到跟原来 `freeTree` 很像的树，只差在最左边多了 `'Z'`。

### I'm going straight to top, oh yeah, up where the air is fresh and clean!

写一个函数走到树的最顶端是很简单的：

```haskell
topMost :: Zipper a -> Zipper a  
topMost (t,[]) = (t,[])  
topMost z = topMost (goUp z)
```

如果我们的面包屑都没了，就表示我们已经在树的 root，我们便回传目前的锁定目标。晡然，我们便往上走来锁定到父节点，然后递归地调用 `topMost`。我们现在可以在我们的树上四处移动，调用 `modify` 或 `attach` 进行我们要的修改。我们用 `topMost` 来锁定到 root，便可以满意地欣赏我们的成果。

## 来看串列

Zippers 几乎可以套用在任何数据结构上，所以听到他可以被套用在 list 上可别太惊讶。毕竟，list 就是树，只是节点只有一个儿子，当我们实作我们自己的 list 的时候，我们定义了下面的型态：

```haskell
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
```

![](/files/-LjUmqZ_j7GL7EkxCalc)

跟我们二元树的定义比较，我们就可以看出我们把 list 看作树的原则是正确的。

一串 list 像是 `[1,2,3]` 可以被写作 `1:2:3:[]`。他由 list 的 head`1` 以及 list 的 tail `2:3:[]` 组成。而 `2:3:[]` 又由 `2` 跟 `3:[]` 组成。至于 `3:[]`，`3` 是 head 而 tail 是 `[]`。

我们来帮 list 做个 zipper。list 改变锁定的方式分为往前跟往后（tree 分为往上，往左跟往右）。在树的情形中，锁定的部份是一棵子树跟留下的面包屑。那究竟对于一个 list 而言一个面包屑是什么？当我们处理二元树的时候，我们说面包屑必须代表 root 的父节点跟其他未走过的子树。他也必须记得我们是往左或往右走。所以必须要有除了锁定的子树以外的所有信息。

list 比 tree 要简单，所以我们不需要记住我们是往左或往右，因为我们只有一种方式可以往 list 的更深层走。我们也不需要哪些路径我们没有走过的信息。似乎我们所需要的信息只有前一个元素。如果我们的 list 是像 `[3,4,5]`，而且我们知道前一个元素是 `2`，我们可以把 `2` 摆回 list 的 head，成为 `[2,3,4,5]`。

由于一个单一的面包屑只是一个元素，我们不需要把他摆进一个型态里面，就像我们在做 tree zippers 时一样摆进 `Crumb`：

```haskell
type ListZipper a = ([a],[a])
```

第一个 list 代表现在锁定的 list，而第二个代表面包屑。让我们写一下往前跟往后走的函数：

```haskell
goForward :: ListZipper a -> ListZipper a  
goForward (x:xs, bs) = (xs, x:bs)  

goBack :: ListZipper a -> ListZipper a  
goBack (xs, b:bs) = (b:xs, bs)
```

当往前走的时候，我们锁定了 list 的 tail，而把 head 当作是面包屑。当我们往回走，我们把最近的面包屑欻来然后摆到 list 的最前头。

来看看两个函数如何运作：

```haskell
ghci> let xs = [1,2,3,4]  
ghci> goForward (xs,[])  
([2,3,4],[1])  
ghci> goForward ([2,3,4],[1])  
([3,4],[2,1])  
ghci> goForward ([3,4],[2,1])  
([4],[3,2,1])  
ghci> goBack ([4],[3,2,1])  
([3,4],[2,1])
```

我们看到在这个案例中面包屑只不过是一部分反过来的 list。所有我们走过的元素都被丢进面包屑里面，所以要往回走很容易，只要把信息从面包屑里面捡回来就好。

这样的形式也比较容易看出我们为什么称呼他为 Zipper，因为他真的就像是拉链一般。

如果你正在写一个文本编辑器，那你可以用一个装满字串的 list 来表达每一行文本。你也可以加一个 Zipper 以便知道现在光标移动到那一行。有了 Zipper 你就很容易的可以添加或删除现有的每一行。

## 阳春的文件系统

理解了 Zipper 是如何运作之后，我们来用一棵树来表达一个简单的文件系统，然后用一个 Zipper 来增强他的功能。让我们可以在文件夹间移动，就像我们平常对文件系统的操作一般。

这边我们采用一个比较简化的版本，文件系统只有文件跟文件夹。文件是数据的基本单位，只是他有一个名字。而文件夹就是用来让这些文件比较有结构，并且能包含其他文件夹与文件。所以说文件系统中的组件不是一个文件就是一个文件夹，所以我们便用如下的方法定义型态：

```haskell
type Name = String  
type Data = String  
data FSItem = File Name Data | Folder Name [FSItem] deriving (Show)
```

一个文件是由两个字串组成，代表他的名字跟他的内容。一个文件夹由一个字串跟一个 list 组成，字串代表名字，而 list 是装有的组件，如果 list 是空的，就代表他是一个空的文件夹。

这边是一个装有些文件与文件夹的文件夹：

```haskell
myDisk :: FSItem  
    myDisk = 
        Folder "root"   
            [ File "goat_yelling_like_man.wmv" "baaaaaa"  
            , File "pope_time.avi" "god bless"  
            , Folder "pics"  
                [ File "ape_throwing_up.jpg" "bleargh"  
                , File "watermelon_smash.gif" "smash!!"  
                , File "skull_man(scary).bmp" "Yikes!"  
                ]  
            , File "dijon_poupon.doc" "best mustard"  
            , Folder "programs"  
                [ File "fartwizard.exe" "10gotofart"  
                , File "owl_bandit.dmg" "mov eax, h00t"  
                , File "not_a_virus.exe" "really not a virus"  
                , Folder "source code"  
                    [ File "best_hs_prog.hs" "main = print (fix error)"  
                    , File "random.hs" "main = print 4"  
                    ]  
                ]  
            ]
```

这就是目前我的磁盘的内容。

### A zipper for our file system

![](/files/-LjUmqZby284IRHMO34q)

我们有了一个文件系统，我们需要一个 Zipper 来让我们可以四处走动，并且增加、修改或移除文件跟文件夹。就像二元树或 list，我们会用面包屑留下我们未走过路径的信息。正如我们说的，一个面包屑就像是一个节点，只是他包含所有除了我们现在正锁定的子树的信息。

在这个案例中，一个面包屑应该要像文件夹一样，只差在他缺少了我们目前锁定的文件夹的信息。为什么要像文件夹而不是文件呢？因为如果我们锁定了一个文件，我们就没办法往下走了，所以要留下信息说我们是从一个文件走过来的并没有道理。一个文件就像是一棵空的树一样。

如果我们锁定在文件夹 `"root"`，然后锁定在文件 `"dijon_poupon.doc"`，那面包屑里的信息会是什么样子呢？他应该要包含上一层文件夹的名字，以及在这个文件前及之后的所有项目。我们要的就是一个 `Name` 跟两串 list。借由两串 list 来表达之前跟之后的元素，我们就完全可以知道我们目前锁定在哪。

来看看我们面包屑的型态：

```haskell
data FSCrumb = FSCrumb Name [FSItem] [FSItem] deriving (Show)
```

这是我们 Zipper 的 type synonym：

```haskell
type FSZipper = (FSItem, [FSCrumb])
```

要往上走是很容易的事。我们只要拿现有的面包屑来组出现有的锁定跟面包屑：

```haskell
fsUp :: FSZipper -> FSZipper  
fsUp (item, FSCrumb name ls rs:bs) = (Folder name (ls ++ [item] ++ rs), bs)
```

由于我们的面包屑有上一层文件夹的名字，跟文件夹中之前跟之后的元素，要往上走不费吹灰之力。

至于要往更深层走呢？如果我们现在在 `"root"`，而我们希望走到 `"dijon_poupon.doc"`，那我们会在面包屑中留下 `"root"`，在 `"dijon_poupon.doc"` 之前的元素，以及在他之后的元素。

这边有一个函数，给他一个名字，他会锁定在在现有文件夹中的一个文件：

```haskell
import Data.List (break)  

fsTo :: Name -> FSZipper -> FSZipper  
fsTo name (Folder folderName items, bs) =   
  let (ls, item:rs) = break (nameIs name) items  
  in  (item, FSCrumb folderName ls rs:bs)  

nameIs :: Name -> FSItem -> Bool  
nameIs name (Folder folderName _) = name == folderName  
nameIs name (File fileName _) = name == fileName
```

`fsTo` 接受一个 `Name` 跟 `FSZipper`，回传一个新的 `FSZipper` 锁定在某个文件上。那个文件必须在现在身处的文件夹才行。这函数不会四处找寻这文件，他只会看现在的文件夹。

![](/files/-LjUmqZdUXPot_wcnbDa)

首先我们用 `break` 来把身处文件夹中的文件们分成在我们要找的文件前的，跟之后的。如果记性好，`break` 会接受一个 predicate 跟一个 list，并回传两个 list 组成的 pair。第一个 list 装有 predicate 会回传 `False` 的元素，而一旦碰到一个元素回传 `True`，他就把剩下的所有元素都放进第二个 list 中。我们用了一个辅助函数叫做 `nameIs`，他接受一个名字跟一个文件系统的元素，如果名字相符的话他就会回传 `True`。

现在 `ls` 一个包含我们要找的元素之前元素的 list。`item` 就是我们要找的元素，而 `rs` 是剩下的部份。有了这些，我们不过就是把 `break` 传回来的东西当作锁定的目标，来建造一个面包屑来包含所有必须的信息。

如果我们要找的元素不在文件夹中，那 `item:rs` 这个模式会符合到一个空的 list，便会造成错误。如果我们现在的锁定不是一个文件夹而是一个文件，我们也会造成一个错误而让程序当掉。

现在我们有能力在我们的文件系统中移上移下，我们就来尝试从 root 走到 `"skull_man(scary).bmp"` 这个文件吧：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsTo "skull_man(scary).bmp"
```

`newFocus` 现在是一个锁定在 `"skull_man(scary).bmp"` 的 Zipper。我们把 zipper 的第一个部份拿出来看看：

```haskell
ghci> fst newFocus  
File "skull_man(scary).bmp" "Yikes!"
```

我们接着往上移动并锁定在一个邻近的文件 `"watermelon_smash.gif"`：

```haskell
ghci> let newFocus2 = newFocus -: fsUp -: fsTo "watermelon_smash.gif"  
ghci> fst newFocus2  
File "watermelon_smash.gif" "smash!!"
```

### Manipulating our file system

现在我们知道如何遍历我们的文件系统，因此操作也并不是难事。这边便来写个重命名目前锁定文件或文件夹的函数：

```haskell
fsRename :: Name -> FSZipper -> FSZipper  
fsRename newName (Folder name items, bs) = (Folder newName items, bs)  
fsRename newName (File name dat, bs) = (File newName dat, bs)
```

我们可以重命名 `"pics"` 文件夹为 `"cspi"`：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsRename "cspi" -: fsUp
```

我们走到 `"pics"` 这个文件夹，重命名他然后再往回走。

那写一个新的元素在我们目前的文件夹呢？

```haskell
fsNewFile :: FSItem -> FSZipper -> FSZipper  
fsNewFile item (Folder folderName items, bs) =   
    (Folder folderName (item:items), bs)
```

注意这个函数会没办法处理当我们在锁定在一个文件却要添加元素的情况。

现在要在 `"pics"` 文件夹中加一个文件然后走回 root：

```haskell
ghci> let newFocus = (myDisk,[]) -: fsTo "pics" -: fsNewFile (File "heh.jpg" "lol") -: fsUp
```

当我们修改我们的文件系统，他不会真的修改原本的文件系统，而是回传一份新的文件系统。这样我们就可以访问我们旧有的系统（也就是 `myDisk`）跟新的系统（`newFocus` 的第一个部份）使用一个 Zippers，我们就能自动获得版本控制，代表我们能访问到旧的数据结构。这也不仅限于 Zippers，也是由于 Haskell 的数据结构有 immutable 的特性。但有了 Zipper，对于操作会变得更容易，我们可以自由地在数据结构中走动。

## 小心每一步

到目前为止，我们并没有特别留意我们在走动时是否会超出界线。不论数据结构是二元树，List 或文件系统。举例来说，我们的 `goLeft` 函数接受一个二元树的 Zipper 并锁定到他的左子树：

```haskell
goLeft :: Zipper a -> Zipper a  
goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
```

![](/files/-LjUmqZfk3bCH2chsHb4)

但如果我们走的树其实是空的树呢？也就是说，如果他不是 `Node` 而是 `Empty`？再这情况，我们会因为模式匹配不到东西而造成 runtime error。我们没有处理空的树的情形，也就是没有子树的情形。到目前为止，我们并没有试着在左子树不存在的情形下锁定左子树。但要走到一棵空的树的左子树并不合理，只是到目前为止我们视而不见而已。

如果我们已经在树的 root 但仍旧试着往上走呢？这种情形也同样会造成错误。。用了 Zipper 让我们每一步都好像是我们的最后一步一样。也就是说每一步都有可能会失败。这让你想起什么吗？没错，就是 Monad。更正确的说是 `Maybe` monad，也就是有可能失败的 context。

我们用 `Maybe` monad 来加入可能失败的 context。我们要把原本接受 Zipper 的函数都改成 monadic 的版本。首先，我们来处理 `goLeft` 跟 `goRight`。函数的失败有可能反应在他们的结果，这个情况也不利外。所以来看下面的版本：

```haskell
goLeft :: Zipper a -> Maybe (Zipper a)  
goLeft (Node x l r, bs) = Just (l, LeftCrumb x r:bs)  
goLeft (Empty, _) = Nothing  

goRight :: Zipper a -> Maybe (Zipper a)  
goRight (Node x l r, bs) = Just (r, RightCrumb x l:bs)  
goRight (Empty, _) = Nothing
```

然后我们试着在一棵空的树往左走，我们会得到 `Nothing`:

```haskell
ghci> goLeft (Empty, [])  
Nothing  
ghci> goLeft (Node 'A' Empty Empty, [])  
Just (Empty,[LeftCrumb 'A' Empty])
```

看起来不错。之前的问题是我们在面包屑用完的情形下想往上走，那代表我们已经在树的 root。如果我们不注意的话那 `goUp` 函数就会丢出错误。

```haskell
goUp :: Zipper a -> Zipper a  
goUp (t, LeftCrumb x r:bs) = (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
```

我们改一改让他可以失败得好看些：

```haskell
goUp :: Zipper a -> Maybe (Zipper a)  
goUp (t, LeftCrumb x r:bs) = Just (Node x t r, bs)  
goUp (t, RightCrumb x l:bs) = Just (Node x l t, bs)  
goUp (_, []) = Nothing
```

如果我们有面包屑，那我们就能成功锁定新的节点，如果没有，就造成一个失败。

之前这些函数是接受 Zipper 并回传 Zipper，这代表我们可以这样操作：

```haskell
gchi> let newFocus = (freeTree,[]) -: goLeft -: goRight
```

但现在我们不回传 `Zipper a` 而回传 `Maybe (Zipper a)`。所以没办法像上面串起来。我们在之前章节也有类似的问题。他是每次走一步，而他的每一步都有可能失败。

幸运的是我们可以从之前的经验中学习，也就是使用 `>>=`，他接受一个有 context 的值（也就是 `Maybe (Zipper a)`），会把值喂进函数并保持其他 context 的。所以就像之前的例子，我们把 `-:` 换成 `>>=`。

```haskell
ghci> let coolTree = Node 1 Empty (Node 3 Empty Empty)  
ghci> return (coolTree,[]) >>= goRight  
Just (Node 3 Empty Empty,[RightCrumb 1 Empty])  
ghci> return (coolTree,[]) >>= goRight >>= goRight  
Just (Empty,[RightCrumb 3 Empty,RightCrumb 1 Empty])  
ghci> return (coolTree,[]) >>= goRight >>= goRight >>= goRight  
Nothing
```

我们用 `return` 来把 Zipper 放到一个 `Just` 里面。然后用 `>>=` 来喂到 `goRight` 的函数中。首先我们做了一棵树他的左子树是空的，而右边是有两颗空子树的一个节点。当我们尝试往右走一步，便会得到成功的结果。往右走两步也还可以，只是会锁定在一棵空的子树上。但往右走三步就没办法了，因为我们不能在一棵空子树上往右走，这也是为什么结果会是 `Nothing`。

现在我们具备了安全网，能够在出错的时候通知我们。

我们的文件系统仍有许多情况会造成错误，例如试着锁定一个文件，或是不存在的文件夹。剩下的就留作习题。


# faq


# FAQ

![](https://github.com/MnO2/learnyouahaskell-zh/tree/e47e717ed1af92c4e38a596af5347e7d1434c454/zh-cn/faq/$img/turtle.png)

## 我能把这份教学放在我的网站吗？我可以更改里面的内容吗？

当然。它受[creative commons attribution noncommercial blah blah blah...](http://creativecommons.org/licenses/by-nc-sa/3.0/)许可证的保护，因此你可以分享或修改它。只要你是发自内心想要这么作，并且只将教学用于非商业目的。

## 推荐其它几个 Haskell 读物？

有很多出色的教学，但我得告诉你[Real World Haskell](http://book.realworldhaskell.org/read/)出类拔萃。它太棒了。我觉得它可以和这个教程搭配着一起读。这个教程提供几个简单例子好让初学者对那点概念有个简单认识，而 Real World Haskell 真正教给你如何充分地使用它们。

## 我怎么联系到你？

最好的方式是用寄封 email 给我，email 是 bonus at learnyouahaskell dot com。不过有点耐心，我没有 24 小时在电脑旁，所以如果我没有即时地回复你的 email 的话，请多包含。

## 我想要一些习题！

很快就会有了！很多人不断来问我能不能加些习题，我正在赶工呢。

## 关于作者

我的名字是 Miran Lipovača，住在斯洛文尼亚的 Ljubljana。 大部分时间我都闲闲没事，不过当我认真的时候我不是在写程序，绘画，练格斗技，或弹 bass。 我甚至弄了一个讨论 bass 的网站。 我甚至搜集一堆猫头鹰玩偶，有时候我会对他们自言自语。

## 关于简体译者

我的名字是[Fleurer](http://fleurer-lee.com)，在淄博的山东理工大学读书。电子邮件是 me.ssword at gmail dot com

## 关于繁体译者

我是[MnO2](http://blog.mno2.org)，Haskell 爱好者，


# resource


# Resource

网络上 Haskell 的资源虽不少，但由于目前社区的人力有限。所以比较没能整理成一套能循序渐进的学习方式。常常会在 Haskell Wiki 上撞到对初学者太过于深入的东西。或是觉得奇怪怎么不断有之前没看过的东西冒出来。造成学习 Haskell 很大的撞墙期。这边译者会渐渐补充一些自己觉得有用的资源，尝试找到一些中阶的教材能够衔接初学跟高端。

## Specification

* [Haskell 98 Report](http://www.haskell.org/onlinereport/):  Haskell 的标准，目前 GHC 如果不用任何 Extension，写出来的程序是符合 Haskell 98 的标准。
* [Haskell 2010 Report](http://www.haskell.org/onlinereport/haskell2010/):，最新的标准，有许多已经实作但要开 Extension 才能用。

## Tools

* [Hoogle](http://www.haskell.org/hoogle/): Haskell 函数的搜索引擎，不只可以用函数的名称搜索，也可以用函数的型态来搜索。
* [Hayoo](http://holumbus.fh-wedel.de/hayoo/hayoo.html): 跟 Hoogle 同样功能。
* [hdiff](http://hdiff.luite.com/): 可以方便查找 package 不同版号之间的差异。
* [packdeps](http://packdeps.haskellers.com/): 方便查找 Hackage 上面 package 之间的相依性。

## Lectures & Articles

* [Wikibook Haskell](http://en.wikibooks.org/wiki/Haskell): 丰富的 Wikibook 资源
* [CS240h](http://www.scs.stanford.edu/11au-cs240h/notes/): David Mazières 跟 Bryan O'Sullivan 在 Stanford 开的课。
* [本物のプログラマはHaskellを使う](http://itpro.nikkeibp.co.jp/article/COLUMN/20060915/248215/): Haskell 专栏
* [Write Yourself a Scheme in 48 Hours](http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours)，Audrey Tang 写的教学，教你如何用 Haskell 写出一个 Scheme。
* \[德国大学的 Functional Programming 课程，语言是用 FP （英文授课）]\(<http://video.s-inf.de/*FP.2005-SS-Giesl.(COt).HD_Videoaufzeichnung>)&#x20;
* [Simon Marlow 讲解 parallel haskell 的投影片](http://community.haskell.org/~simonmar/slides/cadarache2012/)&#x20;
* [FLOLAC 2012](http://flolac.iis.sinica.edu.tw/flolac12/doku.php?id=zh-tw:start)
* [ICFP 2012](http://www.youtube.com/channel/UCP9g4dLR7xt6KzCYntNqYcw?\&desktop_uri=%2Fchannel%2FUCP9g4dLR7xt6KzCYntNqYcw)
* [Explanation of Generalized Algebraic Data Types](http://archive.org/details/ExplanationOfGeneralizedAlgebraicDataTypesgadts)
* [A Quick Intro to Snap](http://bonus500.github.com/sc2blog/*title-slide)
* [Logic, Languages, Compilation, and Verification 2012](http://www.cs.uoregon.edu/Research/summerschool/summer12/curriculum.html)
* [Haskell in Halle/Saale](http://iba-cg.de/hal7.html)
* \[Fast Code Nation]\(<http://bos.github.com/reaktor-dev-day-2012/reaktor-talk-slides.html*(1>))

## Forum

* [Stackoverflow](http://stackoverflow.com/questions/tagged/haskell): 著名 stackoverflow 上的\*haskell tag
* [Reddit](http://www.reddit.com/r/haskell/)

## Online Judge

* [H-99: Ninety-Nine Haskell Problems](http://www.haskell.org/haskellwiki/99_questions)&#x20;
* [Project Euler](http://projecteuler.net/): 已经算非常著名的 Online Judge，可惜只有上传答案。如果问题实在想不出来，Haskell Wiki 上也有参考答案。
* [SPOJ](http://www.spoj.pl/): 少数的 Online Judge 系统可以上传 Haskell 的，题目非常丰富。也是练 ACM ICPC 常用的网站。

## Books

* [Learn you a Haskell for great good (Japanese Translation)](http://www.amazon.co.jp/%E4%81%99%E3%81%94%E3%81%84Haskell%E3%81%9F%E3%81%AE%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%BC%E3%81%86-Miran-Lipova%C4%8Da/dp/4274068854)
* [Real World Haskell](http://book.realworldhaskell.org/)
* [Yesod Book, 讲解如何使用 Yesod Web Framework](http://www.yesodweb.com/book)

## PL Researchers

* [穆信成老师](http://www.iis.sinica.edu.tw/~scm/)
* [单中杰老师](http://www.cs.rutgers.edu/~ccshan/)
* [Conal Elliott](http://conal.net/)
* [Edward Yang](http://blog.ezyang.com/)
* [Edward Kmett](http://comonad.com/reader/)

## Interesting Projects

* [Fay Programming Langauge](http://fay-lang.org/) 用 Haskell 语言的子集，直接转译成 Javascript
* [Leksah](http://leksah.org/): Haskell IDE
* [Super Manao Bros](https://github.com/Mokehehe/Monao/): 超级玛利欧

## Taiwan Functional Programming User Group

* [TW-FPUG on Vimeo](http://vimeo.com/groups/140878)
* [Haskell 高端算子件介绍](https://docs.google.com/file/d/0BzqwG7n2gs71blE5V2pzR29WUDQ/edit?pli=1)


