Haskell

**Haskell**（發音為 /ˈhæskəl/）是一種標準化的，通用的純函數式程式語言，有惰性求值和強靜態類型。它的命名源自美國邏輯學家哈斯凱爾・加里，他在數理邏輯方面上的工作使得函式語言程式設計語言有了廣泛的基礎。在 Haskell 中，「函式是頭等物件」。作為一門函式程式語言，主要控制結構是函式。Haskell 語言是 1990 年在程式語言 Miranda 的基礎上標準化的，並且以 λ 演算為基礎發展而來。這也是為什麼 Haskell 語言以希臘字母「λ」（Lambda）作為自己的標誌。Haskell 具有「證明即程式、命題為類型」的特徵。

## 歷史

1985 年，Miranda 發行後，惰性函數式語言的關注度增長。到 1987 年前，出現了十多種非限定性、純函數式語言。其中，Miranda 使用的最為廣泛，但還沒有出現在公共領域。在俄勒岡波特蘭的函式語言程式設計語言與電腦結構大會（FPCA '87）上，參加者一致同意形成一個委員會來為這樣的語言定義一種開放性標準。該委員會旨在整合已有函數式語言，作為將來的函數式語言設計研究工作的基礎。

### Haskell 1.0 至 1.4

能夠確使類型安全的運算子多載的類型類，是由 Philip Wadler 和 Stephen Blott 最初為 Standard ML 提議的，但首先在 Haskell 中於 1987 年至版本 1.0 期間實現。

1990 年定義了 Haskell 的第一個版本（「Haskell 1.0」）。委員會形成了一系列的語言定義（1.0，1.1，1.2，1.3，1.4）。

### Haskell 98

1997 年底，該系列形成了 **Haskell 98**，旨在定義一個穩定、最小化、可移植的語言版本以及相應的標準庫，以用於教學和作為將來擴充的基礎。委員會明確歡迎建立各種增加或整合實驗性特性的 Haskell 98 的擴充和變種。

1999 年 2 月，Haskell 98 語言標準公布，名為《The Haskell 98 Report》。2003 年 1 月，《Haskell 98 Language and Libraries: The Revised Report》公布。接著，格拉斯哥 Haskell 編譯器實現了當時的事實標準，Haskell 快速發展。

### Haskell Prime

2006 年早期，開始了定義 Haskell 98 標準後續的行程，非正式命名為 **Haskell Prime**。這是個修訂語言定義的不斷增補的過程，每年產生一個新的修訂版。第一個修訂版於 2009 年 11 月完成、2010 年 7 月發布，稱作 **Haskell 2010**。

### Haskell 2010

Haskell 2010 加入了外部函式介面（Foreign Function Interface，FFI）允許繫結到其它程式語言，修正了一些語法問題（在正式語法中的改動）並廢除了稱為「n 加 k 模式」（換言之，不再允許形如 `fact (n+1) = (n+1) * fact n` 的定義）。引入了語言級編譯選項語法擴充（Language-Pragma-Syntax-Extension），使得在 Haskell 原始碼中可以明確要求一些擴充功能。Haskell 2010 引入的這些擴充的名字是 DoAndIfThenElse、HierarchicalModules、EmptyDataDeclarations、FixityResolution、ForeignFunctionInterface、LineCommentSyntax、PatternGuards、RelaxedDependencyAnalysis、LanguagePragma、NoNPlusKPatterns。

## 特性

Haskell 是現有的一門開放的、已發布標準的，且有多種實現的語言。支援惰性求值、模式匹配、列表解析、類型類和類型多型。它是一門純函式語言程式設計語言，這意味著大體上，Haskell 中的函式沒有副作用。Haskell 用特定的類型來表達副作用，該類型與函式類型相互獨立。純函式可以操作並返回可執行的副作用的類型，但不能夠執行它們，只有用於表達副作用的類型才能執行這些副作用，Haskell 以此表達其它語言中的非純函式。

Haskell 擁有一個基於 Hindley-Milner 類型推論的靜態、強型別系統。Haskell 在此領域的主要創新就是加入了類型類，原本設想作為多載的主要方式，在之後發現了更多用途。

Haskell 的主要實現 GHC 是個直譯器，也是個原生代碼編譯器。它可以在大多數平台執行，GHC 在並行和並列上具有高效能的實現能力，也有豐富的型別系統，如廣義代數資料類型和類型族（Type Families）。

單子是一個抽象類型，可以表達不同種類的計算，包括例外處理、非確定性、語法分析以及軟體事務記憶體，其中一個應用是用於表達副作用的類型。單子定義為普通的資料類型，同時 Haskell 也為其提供了幾種語法糖。

Haskell 有一個活躍的社群，線上上包倉庫 Hackage 上有豐富的第三方開源庫或工具。

## 語法

### 資料類型

Haskell 是強型別語言。
Char 的字面值用單引號圍起；
字串即 [Char] 類型，其字面值用雙引號括起來。
Int 通常為 32 位元整型
Integer 是無界整型
Float 表示單精度的浮點數
Double 表示雙精度的浮點數
Bool 只有兩種值：True 和 False。

#### List

使用 [] 與逗號分隔符，定義一個 list 的實例。其元素必須具有相同類型。字串是 list 的特例。用：把元素與 list、其他元素連接（cons）起來。: 是右結合的運算子。[1,2,3] 實際上是 1:2:3:[] 的語法糖。兩個 List 合併通過 ++ 運算子實現。按照索引取得 List 中的元素，可以使用 !! 運算子，索引的下標為 0。List 中的 List 可以是不同長度，但必須得是相同的型別。['K'..'Z'] 這樣的 Range 方法快捷定義一個 List。[2,4..20] 用法給出了 Range 的第一、第二、最後一個元素。使用 > 和 >= 可以比較 List 的大小。它會先比較第一個元素，若它們的值相等，則比較下一個，以此類推。List 常用的函式:

* head 返回一個 List 的頭部，也就是 List 的首個元素。
* tail 返回一個 List 的尾部，也就是 List 除去頭部之後的部分。
* last 返回一個 List 的最後一個元素。
* init 返回一個 List 除去最後一個元素的部分。
* length 返回一個 List 的長度。
* null 檢查一個 List 是否為空。如果是，則返回 True，否則返回 False。
* reverse 將一個 List 反轉
* take 返回一個 List 的前幾個元素。例如 take 24 [13,26..] 取前 24 個 13 的倍數
* drop 刪除一個 List 中的前幾個元素
* maximum 返回一個 List 中最大的元素。
* minimun 返回最小的元素。
* sum 返回一個 List 中所有元素的和。
* product 返回一個 List 中所有元素的積。
* elem 判斷一個元素是否在包含於一個 List，通常以中綴函式的形式呼叫
* replicate 得到包含相同元素的 List 。例如：replicate 3 10，得 [10,10,10]。
* repeat 產生一個元素的無限重複的 List
* cycle 接受一個 List 做參數並返回一個無限 List

list comprehension 是指基於一個 List，按照規則產生一個新 List，例如：[x\*2 | x <- [1..10], x\*2 >= 12]

#### Tuple

使用 ( ) 與逗號分隔符，定義一個 tuple 的實例。其元素可以使不同類型，但個數是固定的。

Tuple 的類型取決於其中項的數目與其各自元素的類型。單元素的 Tuple 被禁止。

* fst 返回一個序對的首項。
* snd 返回序對的尾項
* zip 取兩個 List 作為參數，然後將它們依次配對，形成一組序對的 List。

### 運算子

基本類似於 C 語言。但使用 not 表示邏輯非。

```
 +  -  *  /  ^    -- 加、減、乘、除、指數
 mod              -- 取餘數
 $                -- 也是表示函數作用的, 但它的優先級最低, 而且作用次序是從右向左的
 ++               -- 兩個List的連接
 .                 -- 函數的複合
 && ||  ==  /=     -- 與、或、等於、不等於
 <=  >=  <  >      -- 小於等於、大於等於、小於、大於
 : //  =  @        -- 一個元素連接List、
 ->               -- 函數類型描述，運算符左邊為參數類型，右邊為結果類型。為右结合。例如：addThree :: Int -> Int -> Int -> Int 
 =>               -- 運算符的左邊表示一個類型變量（通常為單個小寫字母）屬於一個類型類（Typeclass），相當於C++語言的模板參數類型
 ..               -- List的Range限定
 ::               -- 函數/表達式的類型特徵，左側為函數/表達式，右側為類型
 <-             -- List comprehension 的條件限定
 !!             -- 取List的第某個元素，下標從0開始
```

基本的 Typeclass：

* Eq 可判斷相等性的 type
* Ord 可比較大小的 type
* Show 可表示為字串的 type
* Read 可從字串轉換出值的 type
* Enum 連續的，也就是可列舉的 type。每個值都有後繼 (successer) 和前置 (predecesor)，分別可以通過 succ 函式和 pred 函式得到。
* Bounded 有上限和下限。例如：maxBound :: Char 或者 maxBound :: Bool
* Num 數字
* Integral
* Floating

### 表達式

* let 表達式：格式為 let [bindings] in [expressions] 。let 也可以定義局部函式 。在一行中設定多個名字的值，可用分號隔開。List Comprehension 中 let 繫結的名字在輸出函式及限制條件中都可見；忽略了 let 繫結的 in 部分，因為名字的可見性已經預先定義好了。
* if then else 是表達式
* Case 表達式：

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

### 控制結構

if then else 是分段函式定義時的語法糖。與 C 語言不同，要求必須有 else 部分。類似於 C 語言分支語句的情形，叫做 pattern matching，例子如下：

### 函式

* 函式呼叫有最高運算順序，例如 `succ 9*10` 表示 `(succ 9)*10`。
* 函式的呼叫使用空格符而不是括號。
* 函式的複合呼叫是左結合。
* 首字母大寫的函式是不允許的。
* 兩個參數的函式的呼叫可以寫成中綴形式：`param1 `funcName` param2`。
* 運算子可以用括號圍起來，作為字首形式：`(+) 2 3` 的結果為 5。
* 在 ghci 下，我們可以使用 `let` 關鍵字來定義一個常數。在 ghci 下執行 `let a=1` 與在指令碼中編寫 `a=1` 是等價的。

多型類型 (Polymorphic types) 類似於 C++ 的模板。例如，算術加法：

### lambda 函式

lambda 就是匿名函式。寫法是：一個 `\`（因為它看起來像是希臘字母 λ），後面是用空格分隔的參數，`->` 後面是函式體。通常用括號將括起 lambda 函式，否則它會占據整個右邊部分。

例如：`(\a b -> (a * 30 + 3) / b)`

可以在 lambda 中使用模式匹配，但無法為一個參數設定多個模式，如 `[]` 和 `(x:xs)` 並用。

使用 lambda 可以更明確地表現出值是個函式，可以用來傳遞給其他函式作參數。

### 高階函式

Haskell 的所有函式實際上是單參數函式。多參數函式的寫法實際上是 Curry 化的語法糖。即 `func a b` 等價於 `(func a) b`。

point free style (也稱作 pointless style) 的函式，即通過柯里化 (Currying) 省略掉單參數。例如：

中綴運算子可以加括號變為單參數函式。如 `(*3) 5` 的值為 15。 但 `(-5)` 表示負值，所以單參數函式需要寫為 `(subtract 5)`。

中綴運算子 `$` 可用於改變函式的呼叫次序，使其右邊的表達式先計算出來。這可以減少一對括號使用。例如 `f (g (z x))` 與 `f $ g $ z x` 等價。其定義是：

`$` 還可以將資料作為函式使用。例如：

中綴運算子 `.` 用於函式的複合，其定義是：

### 例外處理

提供了處理異常的函式 `try`、`catch`、`finally`/`etc`. 

輸出結果

```
(1,42)
```

類似於 C++

另外一個例子：

如果僅有一個錯誤條件，`Maybe` 類足夠用了，確省是 Haskell 的 `Monad` class. 更複雜的出錯處理用 `Error` 或 `ErrorT` monads, 類似功能可用 `` `catch` ``。

## 範例

### Haskell 的函式結構

如下是 Haskell 語言的 "Hello world"，注意其中除最後一行外皆可省略。

如下是階乘函式的 Haskell 實現：

它將階乘描述成有一個基本終止情形的遞迴函式。這跟數學定義中對階乘的描述很相似。事實上，Haskell 中很多的代碼的語法與功能都和數學一致。

上面的遞迴函式的第一行是可選的，它描述了這個函式的**型態**（**types**）。它可以讀作**函式 fac 的型態為整數至整數（function fac has a int-to-int type）** 。這就是說，它以一個整型為參數，並且返回另一個整型。

第二行依賴的模式匹配，是 Haskell 程式中一個重要的部分。注意函式的參數是用空格分隔而不是在括號中。當函式的參數是 0 時，它會返回整型 1。對於其他的情況則嘗試第三行。這是一個遞迴，它會一直執行只到滿足基本的情形。負參數會導致無限遞迴，一個 guard 保證第三行不會執行負參數。

"Prelude" 是一個類似 C 中標準庫的小函式集合。使用 Prelude，並用無指定參數的寫法，它可以改成：

上面的定義接近於數學中的定義：_f = g o h_（參見複合函式），這並不是一個對變數賦值的語句。

### Haskell 的高階函式

Haskell 中可以定義高階函式（Higher-order Function），既將函式作為一個參數來使用，也可以將函式作為結果輸出，例如：

這裡 f 就是一個高階函式，它取一個從 Int 到 Int 的函式 g 作為參數，輸出一個從 Int 到 Int 的函式。高階函式的使用在一些情況下將極大的簡化代碼。

## Haskell 的編譯器

* Hugs（頁面存檔備份，存於網際網路檔案館）
* ghc (Glasgow Haskell Compiler)（頁面存檔備份，存於網際網路檔案館）
* yhc (York Haskell Compiler)（頁面存檔備份，存於網際網路檔案館）
* nhc98（頁面存檔備份，存於網際網路檔案館）

## 參考文獻

## 外部連結

* （英文） **Haskell** 的官方首頁（頁面存檔備份，存於網際網路檔案館）
* （英文） HaskellWiki：**Haskell** 社群主網站（頁面存檔備份，存於網際網路檔案館）
* （英文） UNSW 的 Haskell 教程（頁面存檔備份，存於網際網路檔案館）
* （英文） Haskell 笑話（頁面存檔備份，存於網際網路檔案館）
* （英文） Haskell 舊的討論主題
* （英文） Haskell vs. Ada vs. C++ vs. Awk vs. ...... 一個實驗性的前衛語言（PostScript 檔案）
* （英文） Haskell 編程的變革（頁面存檔備份，存於網際網路檔案館） -- 一個輕快的 Haskell 編程風格總纜
* （英文） 一個 Haskell 研究者的線上介紹
* （英文） ePolyglot -- 將 Haskell、Python 與 Eiffel 結合的語言（頁面存檔備份，存於網際網路檔案館）
* （英文） Haskell 函式庫（頁面存檔備份，存於網際網路檔案館）

### 教學手冊

* （英文）用 Haskell 寫程式（頁面存檔備份，存於網際網路檔案館），Graham Hutton 撰寫
* （英文）另一個 Haskell 教學手冊（頁面存檔備份，存於網際網路檔案館） -- 由 Hal Daume III 所寫的好手冊，需要比官方教學手冊少的先備知識
* （英文）A Gentle Introduction to Haskell 98（頁面存檔備份，存於網際網路檔案館）（較高階的教學手冊，有 PDF（頁面存檔備份，存於網際網路檔案館）檔案）
* （英文）Haskell Tutorial for C Programmers，Eric Etheridge 撰寫
* （英文）在 Haskell.org 的教學手冊列表（頁面存檔備份，存於網際網路檔案館）
* （英文）Haskell 編程典範（頁面存檔備份，存於網際網路檔案館）
* （繁體中文）Haskell 趣學指南（頁面存檔備份，存於網際網路檔案館）
* （簡體中文）Haskell 趣學指南（頁面存檔備份，存於網際網路檔案館）