Haskell的類型系統
- 強類型:類型轉換必須顯示使用類型轉換函數。
- 靜態:不是python這種動態語言。java這樣的編譯時期就能發現類型不正確的表達式。Haskell提供的typeclass機制提供了大部分動態類型的特點。
- 可以通過自動推導得出
一些常用的基本類型
- Char: 單個Unicode字符
- Bool:一個布爾邏輯值。只有2個值。 True和False
- Int:帶符號的定長整數
- Integer: 不限長度的帶符號整數。對Integer的計算不會造成溢出。因此使用Integer的計算結果更可靠。
- Double:浮點數,長度由機器決定,通常是64位。
::
這個符號用來標識類型.例如:: T
就是像Haskell表示,exp的類型是T.:: T
就是exp的類型簽名。如果一個表達式沒有顯示地指名類型,它的類型就通過自動推導決定。
Prelude> :type 'a'
'a' :: Char
Prelude> 'a' -- 自動推導
'a'
Prelude> 'a' :: Char -- 顯式簽名
'a'
類型簽名必須正確。
Prelude> 'a' :: Int -- 試圖將一個字符值標識為 Int 類型
<interactive>:7:1:
Couldn't match expected type `Int' with actual type `Char'
In the expression: 'a' :: Int
In an equation for `it': it = 'a' :: Int
調用函數
要調用一個函數,首先寫出它的名字.后面接函數的參數
Prelude> odd 3
True
Prelude> odd 6
False
注意.函數的參數不需要括號來包圍,參數與參數直接直接通過空格來隔開.
Prelude> compare 2 3
LT
Prelude> compare 3 3
EQ
Prelude> compare 3 2
GT
函數應用的優先級比操作符要高.
Prelude> (compare 2 3) == LT
True
Prelude> compare 2 3 == LT
True
復合數據類型:列表和元組
復合類型通過其他類型構建得出.Haskell中最常用的是復合類型數據是列表跟元組.
- String 是[char]的別名,[Char]表示由Char類型組成的列表.
因為列表中的值可以是任意類型,所以列表是類型多態的.當編寫帶有多態類型的代碼時,需要使用類型變量.這些類型變量用小寫字母開頭,作為一個占位符,最終被一個具體的類型替代(有點像java的泛型)
[a] 表示一個'類型為a的列表' - 元組:跟列表的兩個屬性相反,元組長度固定,但可以包含不同類型的值.
Haskell 有一個特殊的類型(),這種類型只有一個值(),它的作用相當于包含零個元素的元組,類似于C語言中的void:
Prelude> :t ()
() :: ()
元組通常用于以下兩個地方:
- 如果一個函數需要返回多個值,可以將這些值包裝到一個元組中,然后返回元組作為函數的值.
- 當需要使用定長容器,又沒必要使用自定義的類型時,就可以使用元組來對值進行包裝.
處理列表和元組的函數
- take和drop.接受兩個參數,一個數字n和一個列表l.
Prelude> take 2 [1, 2, 3, 4, 5]
[1,2]
Prelude> drop 2 [1, 2, 3, 4, 5]
[3,4,5]
- fst,snd接受一個二元組作為參數,返回該元組的第一個和第二個元素。
Prelude> fst (1, 'a')
1
Prelude> snd (1, 'a')
'a'
將表達式傳給函數
Haskell函數應用是左關聯的。比如說,表達式a b c d 等同于(((a b) c) d)。要將一個表達式用作另一個表達式的參數。必須顯式的使用括號來包圍它。
Prelude> head (drop 4 "azety")
'y'
函數類型
使用 :type命令可以查看函數的類型[縮寫形式為:t]
Prelude> :type lines
lines :: String -> [String]
通過類型簽名顯示,lines函數接受單個字符串,并返回包含字符串值的列表。
純度
副作用:假設有某個函數,它讀取并返回某個全局變量,如果程序中的其他代碼可以修改這個全局變量,那么這個函數的返回值就取決于這個全局變量在某一時刻的值。
Haskell的函數在默認情況下都是無副作用的。函數的結果只取決于顯式傳入的參數。
帶副作用的函數稱為“不純(impure)函數”。不帶副作用的函數稱為“pure函數“
從類型簽名可以看出一個Haskell函數是否帶有副作用 -不純的函數類型簽名都以IO開頭:
Prelude> :type readFile
readFile :: FilePath -> IO String
Haskell源碼,以及簡單函數的定義
ghci只支持Haskell特性的一個非常受限的子集。因此,將代碼寫在源碼文件里。
Haskell源碼通常以.hs作為后綴.我們創建一個add.hs文件,并將以下定義添加到文件中:
-- file: ch02/add.hs
add a b = a + b
=
號左邊的add a b
是函數名和函數參數,而右邊的a+b
則是函數體,符號=
表示將左邊的名字(函數名和函數參數)定義為右邊的表達式(函數體)。
將add.hs保存后,可以在ghci里通過: load (縮寫為:l)載入。然后直接調用add函數即可。
注意,Haskell里一個函數就是一個單獨的表達式,而不是一組陳述。所以不適用return關鍵字來返回函數值。
變量
- 在Haskell中,一旦變量綁定了某個表達式,name這個變量的值就不會改變,我們總能用這個變量來指代它所關聯的表達式,而且每次都會得到同樣的結果
- 在聲明式語言中,變量的值可能無時無刻都處在改變當中。
條件求值
Haskell中的if表達式。
首先看自己實現的一個drop函數
myDrop n xs = if n <= 0 || null xs
then xs
else myDrop (n - 1) (tail xs)
if 關鍵字引入了一個帶有三個部分的表達式:
- 跟在if之后的是一個Bool類型表達式,是if的條件部分
- then 之后的是另一個表達式。這個表達式表示在if條件為
True時執行 - else之后的是另一個表達式,在條件部分是False時被執行。
- 跟在
then
跟else
之后的表達式稱為"分支".不同分支之間的類型必須相同.如果是if True then 1 else "foo"
這樣的表達式會產生錯誤,因為兩個分支的類型并不相同。
通過示例了解求值
惰性求值
isOdd n = mod n 2 == 1
一般而言,使用上面的isOdd函數計算isOdd (2+1),都是先計算2+1 = 3 然后在用isOdd的函數定義來進行判斷.
但是在Haskell中.并不需要先計算出(2+1).只用把這個表達式存在塊中.當這個子表達式的值真正被需要時才進行計算.
Haskell里的多態.
觀察last函數.這個函數返回列表中的最后一個元素,不管列表中元素的類型是int或者string.
last函數簽名:
last :: [a] -> a
a可以指代任意類型.