Haskell 入門筆記(四)
型別系統
強大的型別系統是 Haskell的 一個非常大的優勢。
Haskell 所有表示式型別在編譯時判斷。這樣的話,可以使得程式碼更加安全,比如說,拿一個整數和一個字串進行除法運算是沒辦法進行的,那麼在編譯器就會直接報錯,不會等到執行時程式崩潰才知道。Haskell 與 Java 不一樣,Haskell 能夠進行型別推斷
(Type Inference),也就是說,你不需要明確的說 100 是個數字,或者說是整型,編譯時能推斷出這是一個整型。
在 GHCi 中,我們可以使用 :t
命令來檢測一個表示式的型別。
Prelude> :t 'q'
'q' :: Char
Prelude> :t "aaa"
"aaa" :: [Char]
::
操作符的含義是「具有 … 型別」。也就是說,根據上面的結果,我們知道,字元
q 的型別是 「Char」。一般來說,Haskell 的型別的首字母都是大寫,比如上面提到的 Char,還有 Bool 或者 Boolean。[]
代表 List,[Char]
代表元素型別為 Char 的 List。()
則代表 Tuple,('a','a')
的型別是 (Char,Char)
。
顯式型別宣告
除了表示式之外,函式也是有型別的。我們在定義函式的時候,可以顯式地給函式宣告其型別。我們在前面講過一個去除字串中大寫字母的 List Comprehension:
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
對於這樣一個函式,很明顯,其輸入和輸出都是字串,也就是字元的 List,因此,我們可以這樣宣告函式的型別:
removeNonUppercase :: [Char]->[Char]a
上面這個宣告的含義是,函式 removeNonUppercase
接收一個[Char]
型別的引數(例如字串),並且返回一個 [Char]
(例如字串)。那怎麼去指定一個接收多個引數的函式的型別呢?比如說有一個函式叫 addThree
,接收三個引數,將這三個引數的值相加並且返回。我們可以這樣指定 addThree 的函式型別:
addThree :: Int->Int->Int->Int
也就是說,最後一個會被當做返回值來解析,前面的都會被當做引數來解析。如果說你不知道你要寫的函式到底應該是什麼型別,你可以先把函式寫出來,然後使用 :t
命令看看到底是什麼型別,最後再補上函式型別。
常見的Haskell型別
型別 | 說明 |
---|---|
Int | 整型,但是能表示的整數有界限(達到一定程度就會溢位),效率更高 |
Integer | 整型,能夠表示的整數沒有界限,效率低 |
Float | 單精度浮點數 |
Double | 雙精度浮點數 |
Bool | 布林值,只有 True 和 False 兩個值 |
Char | 單個Unicode字元 |
Tuple | 具體的 Tuple 型別取決於元素的型別和個數,理論上有無數 Tuple 型別,但是實際上Tuple最多隻能有63個元素 |
型別變數(Type Variable)
有時候函式需要能夠處理多種型別的資料,我們以 head
函式為例。首先看看 head 函式的型別:
Prelude> :t head
head :: [a] –> a
我們可以看到,函式 head
接收一個 List 作為輸入,返回 List 中的一個元素。但是這個元素到底是 Char 還是 Int 還是 Bool 並不重要。這個 a 是什麼?我們說過所有的型別都是以大寫字母打頭的,a 顯然不是一種我們所不知道的型別。a 實際上就是我們這裡說的型別變數的一個例子。型別變數能夠允許函式以一種安全的方式操作多種型別,這一點類似於 Java 中的泛型。使用型別變數的函式在 Haskell 中稱為多型函式(Polymorphyc function)。head 函式的定義的含義是:head 接收一個裝有任何元素的 List,返回這種型別的一個值。英語中單詞 a
也表示泛指, a pen, a apple 等等。
我們再看看 fst 函式的型別定義:
Prelude> :t fst
fst :: (a, b) –> a
這個函式接收一個 pair
,然後返回第一個元素,至於這個 pair 的元素可以是任何型別,這裡的a,b都是型別變數。需要說明的是,這裡的 a 和 b 雖然都是型別變數,但是不意味著他們一定是不同的型別。a,b 這種型別變數就像佔位符變數一樣,表示這個地方有一個某某型別的變數。
Type Class
Type Class 我也不知道該怎麼翻譯比較合適。Type Class 實際上是一種介面,它定義一些行為,當某個變數是這個 Type Class 的例項時,那麼它可以實現這個 Type Class 所描述的行為。Type Class 一般指定一組函式,一個變數是該 Type Class 的例項,我們就需要確定這些函式對於這個變數本身有什麼意義(也就是說這個變數要有自己的實現)。
定義相等性的 Type Class 就是一個很好的例子。很多型別都可以用 ==
來看值是否相等。我們先看看 ==
運算子的函式簽名:
Prelude> :t (==)
(==) :: Eq a => a -> a –> Bool
實際上 ==
是一個函式,基本上 +
,-
,*
以及幾乎所有的運算子都是函式。這裡出現了一個新的符號 =>
,所有出現在這個符號之前的部分叫做 class constraint(類的約束)。這個函式型別的意思是:==
函式接收兩個值,他們同樣屬於型別 Eq
,函式最終返回一個 Bool 值。
Eq
就屬於 Type Class,它提供了判斷值是否相等的介面。而這些值必須是相同型別才有比較的意義,這些值可以是 Eq
的例項。事實上,在標準的 Haskell 中,幾乎所有型別都是 Eq
的例項。需要特別指出的是,Type Class 並不是物件導向程式語言中的 Class。下面我們一起看看 Haskell 中常見的幾種 Type Class:
- Eq
Eq
用來提供檢測值是否相等的介面。它的兩個實現是 ==
和 /=
。這意味著如果在一個函式的定義中出現了 Eq class constraint,那麼這個函式的定義中肯定用到了 ==
或者是 /=
。如果一種型別實現一個函式,他就要定義使用這個型別的值時,該函式到底做些什麼。我們看幾個 Eq 例項進行相等性比較時的例子:
Prelude> 5 == 5
True
Prelude> 'q' == 'q'
True
Prelude> "Hello"=="hello"
False
Prelude> "Hello"=="Hello"
True
Prelude> pi == 3.14
False
我們可以看到,字串的比較規則是遵循 List 的相等性比較,與 Java 中的比較引用是不一樣的。
- Ord
Ord
是一種為那些可以將值放在某種順序排列中的型別設計的 Type Class。我們看看 >
函式的型別:
Prelude> :t (>)
(>) :: Ord a => a -> a –> Bool
>
與 ==
比較類似,都接收兩個引數,然後返回一個 Bool 值。Ord Type Class 涉及到了所有的比較函式:>
、<
、 >=
、 <=
。
compare
函式接收兩個引數,這兩個引數的型別都是 Ord
的例項,然後返回一個 Ordering
。Ordering 是一個值可以是 GT、LT 或者 EQ 的型別,分別代表大於、小於和等於。我們看幾個例子:
Prelude> "abcd" `compare` "bbcd"
LT
Prelude> "abcd" `compare` "abbd"
GT
Prelude> "abcd" `compare` "abcd"
EQ
- Show
型別是 Show
這個 Type Class 的例項的值可以被顯示為字串。對於所有屬於 Show 這個 Type Class 的例項的型別來說,使用最多的函式式 show(s小寫)。我們看幾個例子:
Prelude> show 3
"3"
Prelude> show True
"True"
- Read
Read 可以看做是 Show 的反面。read 函式接收一個字串,然後返回一個型別是 Read 的例項的值。看例子:
Prelude> read "True" || False
True
Prelude> read "5"-2
3
Prelude> read "[1,2,3,4]" ++ [5]
[1,2,3,4,5]
目前為止都一切正常,我們再看一個例子:
Prelude> read "5"
<interactive>:30:1:
Ambiguous type variable `a0' in the constraint:
(Read a0) arising from a use of `read'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: read "5"
In an equation for `it': it = read "5"
當我們直接 read "5"
時,GHCi 不知道該返回什麼。我們之前的例子都將 read 返回的結果再參與某種運算,這樣 GHCi 才好進行型別推斷,這就是為什麼 read "5"
沒辦法返回值的原因。我們看一下read 函式的型別:
Prelude> :t read
read :: Read a => String –> a
我們看到,read 函式接收 String,但是返回一個型別是 Read 的例項的值。但是型別是 Read 例項的型別太多了,GHCi 不知道到底選哪一種型別。這種情況下,我們可以使用型別註解(type annotation)。我們看例子是最直接的:
Prelude> read "5" :: Int
5
Prelude> read "5" :: Float
5.0
對於 read 來說還需要舉一個例子:
Prelude> [read "True",False,True,False]
[True,False,True,False]
因為 List 中的每一個元素必須屬於同種型別,所以 read "True"
的返回值必須和其他元素型別一樣,也就是 Bool,這樣,GHCi 就知道該怎麼返回值了。
- Enum
Enum 的例項是那種值有序的型別——他們的值可以被列舉。Enum Type Class 最大的優勢是可以在 Ranges 中使用其值。他們還定義了successors 和 predecessors, 我們可以分別通過 succ 和 pred 兩個函式獲得。Bool、Char、Ordering、Int、Integer、Float、Double 是這個 Type Class 的例項,我們看例子:
Prelude> ['a'..'e']
"abcde"
Prelude> [LT .. GT]
[LT,EQ,GT]
Prelude> [3 .. 5]
[3,4,5]
Prelude> succ 'B'
'C'
Prelude> pred 'B'
'A'
- Bounded
那些是 Bounded 例項的型別有一個上限值和一個下限值。分別可以使用 minBound 和 maxBound 檢視:
Prelude> minBound::Int
-2147483648
Prelude> maxBound::Int
2147483647
minBound 和 maxBound 的型別都是 Bounded a=>a
。準確來說,他們是多型常量。Tuple 中所有元素型別都是 Bounded 的話,那麼這個 Tuple 也被認為是 Bounded 的例項。
- Num
Num 是數字 Type Class,它的例項都是數字。所有的數字都是多型常量。也就是說我們可以將它制定成 Num 下屬型別中的任何一種:
Prelude> 6::Int
6
Prelude> 6::Float
6.0
要成為 Num Type Class 的例項,這個型別必須要已經是 Eq 和 Show Type Class 的例項。
- Floating
顧名思義,這種 Type Class 的例項型別就是用來儲存浮點數的,就兩種型別 Float 和 Double。
- Integral
包括 Int 和 Integer 兩種。介紹兩個函式 fromIntegral 和 length,先看看兩個函式的簽名,再看看怎麼使用:
Prelude> :t fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b
Prelude> :t length
length :: [a] –> Int
Prelude> fromIntegral (length [1,2,3,4]) + 3.4
7.4
Tips
Type Class 實際上是一個抽象的介面,所以一個型別可以是多種 Type Class 的例項,同樣,一種 Type Class 有很多例項;
有時候一種型別必須先是一種 Type Class 的例項才會被允許成為另一個Type Class 的例項。
相關文章
- vue入門筆記體系(四)computed和watchVue筆記
- 《Haskell趣學指南》筆記之 MonoidHaskell筆記Mono
- 《Haskell趣學指南》筆記之 MonadHaskell筆記
- 《Haskell趣學指南》筆記之模組Haskell筆記
- 《Haskell趣學指南》筆記之函式Haskell筆記函式
- vue入門筆記Vue筆記
- Python入門筆記Python筆記
- linux入門筆記Linux筆記
- selenium 入門筆記筆記
- Redis入門筆記Redis筆記
- ByteBuddy入門筆記筆記
- Python 入門筆記Python筆記
- Go入門筆記Go筆記
- NSIS入門筆記筆記
- DFS入門筆記筆記
- ClickHouse入門筆記筆記
- BFS入門筆記筆記
- 《Haskell趣學指南》筆記之 Applicative 函子Haskell筆記APP
- 《Haskell趣學指南》筆記之基本語法Haskell筆記
- 《Haskell趣學指南》筆記之型別(type)Haskell筆記型別
- Hibernate快速入門筆記筆記
- Android入門筆記12Android筆記
- python入門筆記1Python筆記
- XStream入門使用筆記筆記
- 《Haskell趣學指南》筆記之自定義型別Haskell筆記型別
- 《Haskell趣學指南》讀書筆記(2):Type And TypeclassHaskell筆記
- webpack入門學習手記(四)Web
- git入門學習筆記Git筆記
- webpack入門筆記——其他配置Web筆記
- EntityFramework Core筆記:入門(1)Framework筆記
- Docker入門學習筆記Docker筆記
- Unix 入門經典 筆記筆記
- Unity學習筆記--入門Unity筆記
- TS入門學習筆記筆記
- Golang 基礎入門筆記Golang筆記
- 3.Hibernate入門筆記筆記
- 10.Spring入門筆記Spring筆記
- spring框架快速入門筆記Spring框架筆記
- 快應用入門筆記筆記