這一系列的筆記主要參考中文版的 Real World Haskell,這篇博文作為本系列的第一篇浙垫,先介紹一下Haskell的語法和一些基本知識刨仑。
進入/退出交互模式ghci
安裝好Haskell夹姥,命令行敲ghci
進入交互模式
Prelude> :set prompt “ghci>”
ghci>:q -- 退出
基本算術
ghci> 1 + 1
2
ghci> (+) 1 1
2
ghci> 2 + (-1) -- 負數是通過唯一的一元操作符'-'得到,-1的括號不能省略
1
ghci> True && False
False
ghci> True || False
True
ghci> :info (+) -- 操作符的信息可以通過':info'方式查看
class Num a where
(+) :: a -> a -> a
...
-- Defined in ‘GHC.Num’
infixl 6 + -- infixl代表加法是左結合的辙售,優(yōu)先級為6
ghci> :info (^)
(^) :: (Num a, Integral b) => a -> b -> a -- Defined in ‘GHC.Real’
infixr 8 ^ -- 乘方是右結合的轻抱,指數b的類型是Integer,優(yōu)先級為8
ghci> not True -- not是一個內置函數旦部,不可以通過:info查看其信息
False
ghci> 1 == 1
True
ghci> 1 /= 1
False
ghci> let e = exp 1 -- 添加綁定
ghci> e ^ 1
2.718281828459045
ghci> e ** pi -- 指數不是整數時较店,需要更換操作符
23.140692632779263
列表和元組
列表表示一系列相同類型的元素集合容燕,元組則可以包括不同類型的元素梁呈,void在Haskell中被實現為一個空的元組
ghci> [1,2] -- 列表
[1,2]
ghci> [1..5]
[1,2,3,4,5]
ghci> [1,3..9]
[1,3,5,7,9]
ghci> [1] ++ [2,3] -- 列表的連接
[1,2,3]
ghci> 1 : [2,3] -- 元素和列表的連接缰趋,類似cons
[1,2,3]
ghci> head [1,2,3] -- 返回列表的第一個元素
1
ghci> tail [1,2,3] -- 返回除去第一個元素后的剩余列表
[2,3]
ghci> last [1,2,3] -- 返回列表的最后一個元素
[3]
ghci> init [1,2,3] -- 返回除去最后一個元素的剩余列表
[1,2]
ghci> reverse [1,2,3]
[3,2,1]
ghci> take 2 [1,2,3] -- 返回列表的前n個元素組成的新列表
[1,2]
ghci> drop 1 [1,2,3] -- 返回丟棄前n個元素后得到的新列表
[2,3]
ghci> null []
True
ghci> (1, "Hello") -- 元組
(1, "Hello")
ghci> fst (1,"Hello") -- 取元組的第一個元素
1
ghci> snd (1,"Hello") -- 取元組的第二個元素
"Hello"
字符串
Haskell的字符串和C的非常相似秘血,用單引號表示單個字符味抖,雙引號代表字符串灰粮,字符串實際就是單個字符的列表,但不需要像C中用'\0'結尾
ghci> 'a'
'a'
ghci> "Hello World"
"Hello World"
ghci> ['H','e','l','l','o']
"Hello"
ghci> "" == []
True
ghci> 'a' : "bc"
"abc"
ghci> "a" ++ "bc"
"abc"
查看表達式類型
在交互式界面下熔脂,我們有兩種方法查看表達式的類型:
ghci> :set +t
ghci > 'a'
'a'
it :: Char -- it是交互模式下存儲運算結果的變量
ghci> :unset +t
ghci> 'a'
'a'
ghci> :type 'a'
'a' :: Char
ghci> :type 1 + 1
1 + 1 :: Num a => a -- :type執(zhí)行靜態(tài)的類型推導柑肴,并不會顯示運算結果2的類型
分數
ghci> :m +Data.Ratio -- ':m'代表載入模塊
ghci> :set +t
ghci> 1 % 2 -- 分數1/2
1 % 2
it :: Integral a => Ratio a
整數范圍
Haskell中的整數是不限大小的,你可以寫出下面的式子:
ghci> 313 ^ 15
27112218957718876716220410905036741257
自定義函數
在ghci中定義函數的語法和標準Haskell有所不同晰骑,我們在后者環(huán)境下定一個函數add,并加載到ghci環(huán)境中:
-- this is in myDrop.hs
myDrop n xs = if n <= 0 || null xs
then xs
else myDrop (n - 1) (tail xs)
-- back to ghci mode
ghci> :load myDrop.hs
ghci> myDrop 2 "cat"
"t"
副作用
避免函數的副作用有很多好處秽荞,在Haskell中抚官,定義的函數默認都是無副作用的(純函數),有副作用的函數在Haskell中類型會帶有IO標簽:
ghci> :type readFile
readFile :: FilePath -> IO String
純度減輕了理解一個函數所需的工作量凌节。一個純函數的行為并不取決于全局變量、數據庫的內容或者網絡連接狀態(tài)彪见。純代碼(pure code)從一開始就是模塊化的:每個函數都是自包容的娱挨,并且都帶有定義良好的接口捕犬。
將純函數作為默認的另一個不太明顯的好處是酵镜,它使得與不純代碼之間的交互變得簡單柴钻。一種常見的 Haskell 風格就是淮韭,將帶有副作用的代碼和不帶副作用的代碼分開處理。在這種情況下靠粪,不純函數需要盡可能地簡單毫蚓,而復雜的任務則交給純函數去做。
軟件的大部分風險元潘,都來自于與外部世界進行交互:它需要程序去應付錯誤的、不完整的數據翩概,并且處理惡意的攻擊,諸如此類牍鞠。Haskell 的類型系統(tǒng)明確地告訴我們评姨,哪一部分的代碼帶有副作用,讓我們可以對這部分代碼添加適當的保護措施参咙。
通過這種將不純函數隔離、并盡可能簡單化的編程風格择同,程序的漏洞將變得非常少净宵。
變量
在Haskell中,一個變量和一個值建立綁定之后择葡,這個變量的值就不可以再被修改了,舉個例子阻星,下面的代碼是無法通過編譯的:
-- file: ch02/Assign.hs
x = 10
x = 11
命令式語言中的變量是和一個內存地址建立了綁定,但地址里寫的內容是可以反復修改的妥箕,所以在命令式語言中不同時刻讀取同一變量的值可能會得到不同的結果。在Haskell中坎吻,變量是和值建立的綁定宇葱,一個變量被綁定后,我們總能把變量替換成綁定的值黍瞧。
惰性求值
Haskell中用于追蹤未求值表達式的記錄被稱為塊。編譯器通過創(chuàng)建塊來延遲表達式的求值弦讽,直到這個表達式的值真正被需要為止膀哲。如果某個表達式的值不被需要,那么從始至終某宪,這個表達式都不會被求值。在Haskell中可以定義無限長的數組蔼囊,并可以對其進行一些操作:
-- mycons.hs
mycons n = n : mycons (n + 1)
l = mycons 0 -- l是自然數集合
-- ghci
ghci> :load mycons.hs
ghci> take 3 l
[0,1,2]
ghci> (!!) l 0 -- !!是根據下標取列表元素的操作衣迷,從0開始
0
ghci> takeWhile(\x->x<3) l
[0,1,2]
Haskell還自帶了一個repeat::a->[a]
函數,他接受一個參數x壶谒,返回一個無限個x組成的列表。
類型系統(tǒng)
Haskell的類型系統(tǒng)基于簡潔而強大的 System F让禀,這里我們不深究其實現陨界,只列出Haskell類型系統(tǒng)的三個特性:
- 強類型:強類型語言不會做隱式的類型轉換。舉個例子菌瘪,如果將一個整數值作為參數傳給了一個接受浮點數的函數,C 編譯器會自動且靜默(silently)地將參數從整數類型轉換為浮點類型缀皱,而 Haskell 編譯器則會引發(fā)一個編譯錯誤。
- 靜態(tài)類型:靜態(tài)類型的語言會在編譯階段求出所有變量和表達式的類型啤斗,并拒絕執(zhí)行任何類型錯誤的程序赁咙。同時,Haskell還提供了 typeclass 機制讓程序員實現一些動態(tài)類型語言的功能崔拥。
- 自動推導:如果沒有自動推導凤覆,那么一門靜態(tài)語言就要求程序員事無巨細地顯示聲明所有變量的類型以及類型之間的關系,就像Java采用的norminal type system盯桦。和很多函數式語言一樣,Haskell可以幾乎自動推導出所有表達式的類型(有時需要程序員提供一些必要的提示)贴膘。這使得程序員在編碼時幾乎不需要寫任何的類型信息(寫出來當然也是可以的)略号,但同時編譯時期也有強大的類型檢查保證Haskell代碼的類型安全。
多態(tài)
Haskell中列表中的值可以是任意類型玄柠,所以我們可以稱列表為類型多態(tài)(polymorphic)的。任何列表相關的操作函數應當也是多態(tài)的宫患,看一個例子:
ghci> :type last
ghci> last :: [a] -> a
這里last的類型中包含了一個類型變量a铐伴,在不同情況下它會具化成不同的類型,當last函數應用于一個字符串的時候当宴,其類型就是[Char]->Char
,而應用于一個整數數組時玲献,last的類型就是[Integer]->Integer
。