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）。
在GHC 7.10的Haskell prelude中的類型類層級。包括了Foldable和Traversable（相應的改變了一些函數的類型簽名），和作為Functor與Monad中介的Applicative，它們出自Haskell 2010標準。

**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在併發和並行上具有高性能的實現能力，也有豐富的類型系統，如廣義代數數據類型和類型家族）。
單子是一個抽象類型，可以表達不同種類的計算，包括異常處理、非確定性、語法分析以及軟件事務內存，其中一個應用是用於表達副作用的類型。單子定義為普通的數據類型，同時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 = 12

**Tuple**
使用( )與逗號分隔符，定義一個tuple的實例。其元素可以使不同類型，但個數是固定的。
Tuple的類型取決於其中項的數目與其各自元素的類型。單元素的 Tuple 被禁止。
*fst 返回一個序對的首項。
*snd 返回序對的尾項
*zip 取兩個 List作為參數，然後將它們依次配對，形成一組序對的 List。

**運算符**
基本類似於C語言。但使用not表示邏輯非。
+  -  *  /  ^    -- 加、減、乘、除、指數
mod              -- 取餘數
$                -- 也是表示函數作用的, 但它的優先級最低, 而且作用次序是從右向左的
++               -- 兩個List的連接
.                 -- 函數的複合
&& 

** /=     -- 與、或、等於、不等於
**       -- 小於等於、大於等於、小於、大於
: //  =  @        -- 一個元素連接List、
->               -- 函數類型描述，運算符左邊為參數類型，右邊為結果類型。為右結合。例如：addThree :: Int -> Int -> Int -> Int 
**>               -- 運算符的左邊表示一個類型變量（通常為單個小寫字母）屬於一個類型類（Typeclass），相當於C++語言的模板參數類型
..               -- List的Range限定
::               -- 函數/表達式的類型特徵，左側為函數/表達式，右側為類型
result  
pattern -> result  
pattern -> result  
...  

**控制結構**
if then else是分段函數定義時的語法糖。與C語言不同，要求必須有else部分。類似於C語言分支語句的情形，叫做pattern matching，例子如下：
pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts x

x  Bool -> Bool  -- 或操作的類型與定義
True  
_ = True             -- 第二個參數是任何值都匹配。 
False 
y = y

**函數**
 函數調用有最高運算順序，例如succ 9*10表示(succ 9)*10。- 函數的調用使用空格符而不是括號。- 函數的複合調用是左結合。- 首字母大寫的函數是不允許的。- 兩個參數的函數的調用可以寫成中綴形式：param1 `funcName` param2。- 運算符可以用括號圍起來，作為前綴形式：(+) 2 3 的結果為5。- 在 ghci 下，我們可以使用 let 關鍵字來定義一個常量。在 ghci 下執行 let a=1 與在腳本中編寫 a=1 是等價的。
--funcName arguments = expression  --定義函數的一般形式
area r = pi * r ^ 2 -- 定義了一個函數
area 101 -- 調用了函數
f1 f2 3.14 -- 函數調用是左結合，等效於(f1 f2) 3.14
--模式匹配方式定義
factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1)   
--as模式，是將一個名字和 @ 置於模式前，可以在按模式分割參數值時仍保留對其整體的引用。如nameGlobal@(x:y:ys)，nameGlobal會匹配出與 x:y:ys 對應的東西。as模式在較大的模式中保留對整體的引用，從而減少重複性的工作。
heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))
where                     -- where在表達式中局部綁定了名字s與一個值。也可以在表達式之前用let ... in語法
s = (a + b + c) / 2
absolute x                 -- 絕對值函數，使用了分段函數語法糖（稱作Guards）

x  a -> a -> String  
bmiTell weight height  

bmi  String -> String  
initials firstname lastname = f ++ ". " ++ l ++ "."  
where (f:_) = firstname  
(l:_) = lastname  
--where可以定義函數。在定義一個函數的時候也寫幾個輔助函數擺在 where 綁定中。 而每個輔助函數也可以透過 where 擁有各自的輔助函數。
calcBmis :: (RealFloat a) => (a, a) -> a  
calcBmis xs = bmi w h | (w, h)  type2 -> type3 -- 其中，::表示類型特徵(type signature)，->是右結合，這裡等效於type1 -> (type2->type3)，給定一個type1的輸入參數，返回一個函數(type2->type3)
f1 = (absolute .  area)   -- 函數複合運算符是 . (function composition operator)
多態類型(Polymorphic types)類似於C++的模板。例如，算術加法：
(+) :: (Num a) => a -> a -> a -- Num是typeclass。 =>表示signature restricts 

**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)省略掉單參數。例如：
sum' :: (Num a) => a -> a     
sum' xs = foldl (+) 0 xs   --等號的兩端都有個 xs。 
sum'   = foldl (+) 0     --柯里化 (Currying)，可以省掉兩端的 xs。
中綴運算符可以加括號變為單參數函數。如 (*3) 5 的值為15。 但 (-5) 表示負值，所以單參數函數需要寫為 (subtract 5)。
中綴運算符 $ 可用於改變函數的調用次序，使其右邊的表達式先計算出來。這可以減少一對括號使用。例如 f (g (z x)) 與 f $ g $ z x 等價。其定義是：
($) :: (a -> b) -> a -> b  
f $ x = f x  
$ 還可以將數據作為函數使用。例如：
map ($ 3) (4+),(10*),(^2),sqrt
中綴運算符 . 用於函數的複合，其定義是：
(.) :: (b -> c) -> (a -> b) -> a -> c  
f . g = \x -> f (g x)

**異常處理**
提供了處理異常的函數try、catch、finally/etc.  
import Prelude hiding(catch)
import Control.Exception
instance Exception Int
instance Exception Double
main = do
catch
(catch
(throw (42::Int))
(\e-> print (0,e::Double)))
(\e-> print (1,e::Int))
輸出結果
(1,42)
類似於 C++
#include 
using namespace std;
int main() {
try {
throw (int)42;
} catch (double e) {
cout 
另外一個例子：
do {
-- Statements in which errors might be thrown
} `catch` \ex -> do {
-- Statements that execute in the event of an exception, with 'ex' bound to the exception
}
如果僅有一個錯誤條件，Maybe 類足夠用了，確省是Haskell的 Monad class. 更復雜的出錯處理用Error 或ErrorT monads, 類似功能可用`catch`。

**示例**

**Haskell的函數結構**
如下是Haskell語言的"Hello world"，注意其中除最後一行外皆可省略。
module Main where
main :: IO ()
main = putStrLn "Hello, World!"
如下是階乘函數的Haskell實現：
fac :: Int -> Int
fac 0 = 1
fac n = n * fac (n - 1)
它將階乘描述成有一個基本終止情形的遞歸函數。這跟數學定義中對階乘的描述很相似。事實上，Haskell中很多的代碼的語法與功能都和數學一致。
上面的遞歸函數的第一行是可選的，它描述了這個函數的型態（types）。它可以讀作函數fac的型態為整數至整數（function fac has a int-to-int type）。這就是說，它以一個整型為參數，並且返回另一個整型。
第二行依賴的模式匹配，是Haskell程序中一個重要的部分。注意函數的參數是用空格分隔而不是在括號中。當函數的參數是0時，它會返回整型1。對於其他的情況則嘗試第三行。這是一個遞歸，它會一直執行只到滿足基本的情形。負參數會導致無限遞歸，一個guard保證第三行不會執行負參數。
"Prelude"是一個類似C中標準庫的小函數集合。使用Prelude，並用無指定參數的寫法，它可以改成：
fac = product . enumFromTo 1
上面的定義接近於數學中的定義：f = g o h（參見複合函數），這並不是一個對變量賦值的語句。

**Haskell的高階函數**
Haskell中可以定義高階函數（Higher-order Function），既將函數作為一個參數來使用，也可以將函數作為結果輸出，例如：
f :: (Int -> Int) ->(Int -> Int)
f g = \x -> g x + 5
這裡f就是一個高階函數，它取一個從Int到Int的函數g作為參數，輸出一個從Int到Int的函數。高階函數的使用在一些情況下將極大的簡化代碼。

**Haskell的編譯器**
  Hugsdate=20060203013111 -  ghc (Glasgow Haskell Compiler)date=20060208085900 -  yhc (York Haskell Compiler)date=20060203214652 -  nhc98date=20060203001010 

**參考文獻**

**外部連結**
   Haskell的官方主頁date=20061011005609 -   HaskellWiki：Haskell社群主網站date=20060127230539 -   UNSW的Haskell教程date=20070717111830 -   Haskell笑話date=20140731081743 -   Haskell舊的討論主題-   Haskell vs. Ada vs. C++ vs. Awk vs. ......一個實驗性的前衛語言（PostScript檔案）-   Haskell編程的變革date=20070101192126  -- 一個輕快的Haskell編程風格總纜-   一個Haskell研究者的線上介紹-   ePolyglot -- 將Haskell、Python與Eiffel結合的語言date=20070108044857 -   Haskell函式庫date=20070208020342 

**教學手冊**
  用Haskell寫程式date=20070110212407 ，Graham Hutton撰寫-  另一個Haskell教學手冊date=20090915035309  -- 由Hal Daume III所寫的好手冊，需要比官方教學手冊少的先備知識-  A Gentle Introduction to Haskell 98date=20061011005414 （較高階的教學手冊，有 PDFdate=20061012011607 檔案）-  Haskell Tutorial for C Programmers，Eric Etheridge撰寫-  在Haskell.org的教學手冊列表date=20061011015031 -  Haskell編程典範date=20060830114516 -  Haskell趣學指南date=20120621064324 -  Haskell趣學指南date=20180821160610