基本介紹
Haskell(發音為 /ˈhæskəl/)是一種標準化的,通用純
函式式編程語言,有非限定性語義和強靜態類型。它的命名源自美國邏輯學家Haskell Brooks Curry,他在數學邏輯方面的工作使得函式式程式語言有了廣泛的基礎。在Haskell中,“函式是一等公民”。作為函式式程式語言,主要控制結構是函式。Haskell語言是1990年在程式語言Miranda的基礎上標準化的,並且以
λ演算(Lambda-Calculus)為基礎發展而來。具有“證明即程式、結論公式即程式類型”的特徵。這也是Haskell語言以希臘字母「λ」(Lambda)作為自己標誌的原因。Haskell語言的最重要的兩個套用是GHC(Glasgow Haskell Compiler)和Hugs(一個Haskell語言的
解釋器)。
歷史
1985年,
Miranda發行後,惰性函式式語言的關注度增長。到1987年前,出現了十多種非限定性、純函式式語言。其中,Miranda使用的最為廣泛,但還沒有出現在公共領域。在
俄勒岡波特蘭的函式式程式語言與計算機結構大會(FPCA '87)上,參加者一致同意形成一個委員會來為這樣的語言定義一種開放性標準。該委員會旨在集成已有
函式式語言,作為將來的函式式語言設計研究工作的基礎。
Haskell 1.0至1.4
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》公布。接著,Glasgow Haskell Compiler (GHC)實現了當時的事實標準,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在此領域的主要創新就是加入了類型類(type class),原本構想作為重載的主要方式,在之後發現了更多用途。
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 被禁止。
運算符
基本類似於C語言。但使用not表示邏輯非。
+ - * / ^ -- 加、減、乘、除、指數 mod -- 取餘數 $ -- 也是表示函式作用的, 但它的優先權最低, 而且作用次序是從右向左的 ++ -- 兩個List的連線 . -- 函式的複合 && || == /= -- 與、或、等於、不等於 <= >= < > -- 小於等於、大於等於、小於、大於 : // = @ -- 一個元素連線List、 -> -- 函式類型描述,運算符左邊為參數類型,右邊為結果類型。為右結合。例如:addThree :: Int -> Int -> Int -> Int => -- 運算符的左邊表示一個類型變數(通常為單個小寫字母)屬於一個類型類(Typeclass),相當於C++語言的模板參數類型 .. -- List的Range限定 :: -- 函式/表達式的類型特徵,左側為函式/表達式,右側為類型 <- -- List comprehension 的條件限定 !! -- 取List的第某個元素,based 0
基本的 Typeclass:
表達式
case expression of pattern -> 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 <= 6 = 7 - x | otherwise = 0 (||) :: Bool -> 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``等價的
1 --funcName arguments = expression --定義函式的一般形式 2 area r = pi * r ^ 2 -- 定義了一個函式 3 area 101 -- 調用了函式 4 f1 f2 3.14 -- 函式調用是左結合,等效於(f1 f2) 3.14 5 6 --模式匹配方式定義 7 factorial :: (Integral a) => a -> a 8 factorial 0 = 1 9 factorial n = n * factorial (n - 1) 10 11 as模式,是將一個名字和 @ 置於模式前,可以在按模式分割參數值時仍保留對其整體的引用。如nameGlobal@(x:y:ys),nameGlobal會匹配出與 x:y:ys 對應的東西。as模式在較大的模式中保留對整體的引用,從而減少重複性的工作。12 13 heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))14 where -- where在表達式中局部綁定了名字s與一個值。也可以在表達式之前用let ... in語法15 s = (a + b + c) / 216 17 absolute x -- 絕對值函式,使用了分段函式語法糖(稱作Guards)18 | x < 0 = 0 - x19 | otherwise = x -- 兜底條件20 21 bmiTell :: (RealFloat a) => a -> a -> String 22 bmiTell weight height 23 | bmi <= 18.5 = "You're underweight." 24 | bmi <= 25.0 = "You're normal. " 25 | bmi <= 30.0 = "You're fat." 26 | otherwise = "You're overweight." 27 where bmi = weight / height ^ 2 -- 使用where定義多個名字來避免重複28 29 --where也可以用模式匹配30 initials :: String -> String -> String 31 initials firstname lastname = [f] ++ ". " ++ [l] ++ "." 32 where (f:_) = firstname 33 (l:_) = lastname 34 35 --where可以定義函式。在定義一個函式的時候也寫幾個輔助函式擺在 where 綁定中。 而每個輔助函式也可以透過 where 擁有各自的輔助函式。36 calcBmis :: (RealFloat a) => [(a, a)] -> [a] 37 calcBmis xs = [bmi w h | (w, h) <- xs] 38 where bmi weight height = weight / height ^ 2 39 40 funcName :: type1 -> type2 -> type3 -- 其中,::表示類型特徵(type signature),->是右結合,這裡等效於type1 -> (type2->type3),給定一個type1的輸入參數,返回一個函式(type2->type3)41 42 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)
異常處理
提供了處理異常的函式Template:Haskell/Template:Haskell/Template:Haskell/Template:Haskell.
import Prelude hiding(catch) 2 import Control.Exception 3 instance Exception Int 4 instance Exception Double 5 main = do 6 catch 7 (catch 8 (throw (42::Int)) 9 (\e-> print (0,e::Double)))10 (\e-> print (1,e::Int))
輸出結果
類似於 C++
1#include <iostream> 2 using namespace std; 3 int main() { 4 try { 5 throw (int)42; 6 } catch (double e) { 7 cout << "(0," << e << ")" << endl; 8 } catch (int e) { 9 cout << "(1," << e << ")" << endl;10 }11 }
另外一個例子:
1 do {2 -- Statements in which errors might be thrown3 } `catch` \ex -> do {4 -- Statements that execute in the event of an exception, with 'ex' bound to the exception5 }
如果僅有一個錯誤條件,Template:Haskell類足夠用了,確省是Haskell的Template:Haskell
class. 更複雜的出錯處理用Template:Haskell或Template:Haskellmonads, 類似功能可用Template:Haskell。
語言
以Haskell為基礎的衍生語言有很多種,它們分別是:並行Haskell,擴充Haskell(舊名Goffin),Eager Haskell, Eden, DNA-Hakell 和
面向對象的變體(Haskell++, O'Haskell, Mondrian)。另外Haskell還被作為其他語言設計新功能時的樣板,例如Python中的Lambda標記語句。