我在〈靜態定型與單元測試之爭〉談 過「任何數值都是記憶體中的一組位元,型態賦予這組位元意義,這樣開發者就能得知如何對待這組位元,因此型態也部份解釋了開發者想要程式做哪些事 情。」
也因此,任何程式語言的學習,都得認識型態系統,就結論而言,Haskell 是靜態定型(Static type)、強 型別(Strong type)並具有強大型態推斷(Type inference)能 力的語言。
靜態定型
靜態定型表示編譯器在編譯時期,就可以得知各個值與運算式(expression)的型態,舉例來說,你可以設計一個簡單的函式 (Function):
doubleMe :: Float -> Float
doubleMe x = x + x
雖然還沒有正式要介紹函式,不過,這個函式很簡單,第一行是函式的型態宣告,函式名稱是 doubleMe,Float
              -> Float 表示接受 Float 引數並傳回 Float
            結果,x 是接受引數的參數名稱,函式的傳回值是 x + x 運算式的結果。
你可以將之儲存為 myfuncs.hs,然後在 GHCI 中使用 :l 來載入:

可以看到,doubleMe 3.14 表示使用 3.14 來呼叫函式 doubleMe,
            這沒問題,因為 3.14 被推斷為一個 Float,然而 doubleMe
              "3.14" 就不行,因為 "3.14" 不會是一個 Float,
            靜態定型的 Haskell 會在執行之前,就檢查出這類型態不匹配的錯誤,避免許多執行時期因型態不正確而可能引發的錯誤。
強型別
注意!上面我說 doubleMe 3.14 時,3.14 被推斷為一個 Float,
            因為上面 3.14 這 literal
            本身沒有指定型態,因而編譯器試圖為它推斷一個適合的型態。你可以自行指定型態。例如:

當你指定 3.14::Float 時,表示 3.14 的型態就是 Float,
            可以看到,當你指定 1::Int 或 1::Double並試圖呼叫 doubleMe
            時,就會發生編譯錯誤,因為函式只接受 Float 的引數。
就多數主流的靜態語言來說,不能將 double 之類的值指定給 float
            比較容易理解,因為可以解釋為記憶體長度不同(必要時可以使用 CAST 語法來關閉編譯器檢查),然而 1::Int
            不能指定給 Float 的參數,就比較覺得令人詫異了,在多數主流的靜態語言中,將 int
            指定給 float 之類的變數是允許的。
在 Haskell 中,1::Int 不能指定給 Float
            的參數說明了,Haskell 是座落於強型別這側的語言,強型別意謂著,型態轉換不會自動發生,如果函式預期接受 Float,
            而你給他一個 Int 引數,引數並不會自動轉換為 Float。
Haskell 的型態系統有多嚴格?來看看 Int 與浮點數相加會如何?

在 Haskell 中,會不意外地發生編譯錯誤,這類錯誤當然不會像這邊範例這麼直接發生,而會像是以下這種情況:

[1, 2, 3] 在 Haskell 中會建立一個清單, length
            函式可以取得這個清單的長度,以 Int 傳回,Int 與 3.14
            相加就會引發錯誤。類似地,以下也會發生錯誤:

型態推斷
看到以上的範例,你可能會有疑問,那麼 10 + 3.14 為什麼可以?

如前所述,這是因為編譯器推斷出這兩個 literal(嚴格來說,是推斷出 (10 + 3.14)
            這個運算式)最適合的型態 Fractional,在這邊,:t 是可以在 GHCI
            中用來檢驗型態的指令,你可以隨時用它來檢驗 Haskell 中任何值的型態。
然後,當你令某代數 x 與 y 為某值時,因為前後程式文脈(Context)的沒有型態可供參考(像是 +
            函式這類),編譯器會分別給予的型態會像是:

這也就是為何,x + y 不能通過編譯的原因,如果你想要令其通過編譯,可以使用 fromInteger
            函式,例如:

可以看到,fromInteger 的型態宣告是 Num a => Integer
              -> a,Num 是個 Typeclass,某些程度上,你可以將 Typeclass
            類比為 Java 中的 interface,Num a 表示 a
            必須是個具有 Num 行為的型態,Integer -> a 表示參數型態為
            Integer,而傳回型態為 a。
所有的整數與浮點數都有 Num 的行為,當你執行 fromInteger x + y
            時,首先 fromInteger x 傳回值型態為 Num,現在 +
            兩邊可以進行相加了,接著編譯器會根據 y 決定,最後的傳回值型態會是 Double。
不過,如果你是這麼寫,那麼 fromInteger 就吐了:

記得嗎?fromInteger 的型態宣告是 Num a => Integer
              -> a,而在上面,你的 10 是個 Int,型態轉換在
            Haskell 中不會自動發生,就算是 Int 自動轉為 Integer
            也不行。
fromIntegral(注意!字尾是 ral 不是 er)的型態是 (Integral a,
              Num b) => a -> b,表示 a 必須有 Integral
            的行為,而 b 必須有 Num 的行為,Integral
            也是個 Typeclass,所有整數都有 Integral 的行為,因此就涵蓋了 Int。
            就整個 fromIntegral (10::Int) + 3.14 程式文脈,編譯器最後推斷出的型態會是 Fractional,
            實際上,Fractional 也是個 Typeclass。
實際上,Haskell 的編譯器很強大,如果你沒有指定型態,總是會努力為你推斷出適合的型態,例如,先前的 doubleMe
            可以只寫為:
doubleMe x = x + x
重新在 GHCI 中用 :l 載入,使用 :t
            檢驗看看,編譯器為你推斷為何種型態?

在 Haskell 中,絕大多數的情況下,不聲明型態是可以的,這使得 Haskell 程式碼看起來,會像是動態語言中的變數無需宣告型態(實際上,Haskell 沒有變數,之後會詳述),不過,在 Haskell 中,宣告函式時明確聲明型態,反而是鼓勵的,如果你真的不知道你的函式型態要如何宣告型態,可以像這邊,在 GHCI 中檢驗完之後,再將型態宣告添加至原始碼之中。
基本型態與 Typeclass
暈頭了嗎?也許在 Haskell 中,使用函數式風格並不是最難的,使用正確型態通過編譯才是最難的,因 為嚴格的強型別、靜態型態,使得在 Haskell 中要通過編譯本身就是件難事,因此有「It Compiles! Let's ship it!」的笑話!
然而換取而來的代價是,不少因型態不正確的錯誤,在通過編譯之前都被抓出來了,很多時候確實是如此,我以為自己已經謹慎思考過型態了,編譯器卻 總會抓出我沒想到的部份。
最後,來看看這篇文章中提到的幾個基本型態:
- 
              Int有界整數,如果是 32 位元機器,上下界會分別是 2147483647與-2147483648。
- 
              Integer無界整數,效率比較慢,不過可以儲存大整數,例如 2147483648::Int結果會是-2147483648,2147483648::Integer才會是 2147483648。
- 
              Float與Double分別代表單精度浮點數與倍精度浮點數。 
- 
              BoolTrue與False兩個布林值的型態。
- 
              Char字元型態,之後還會看到,像 “Justin” 這個字串,其實是由字元組成的清單。 
來看看這篇文章中提到的幾個 Typeclass:
- 
              Integral代表所有整數的 Typeclass, Int與Integer具有Integral的行為。
- 
              Floating代表所有浮點數的 Typeclass, Float與Double具有Floating的行為。
- 
              Fractional代表分數的 Typeclass,涵蓋了 Float與Double。
- 
              Num代表所有數字的 Typeclass。 
當然,還有其他的,不過,認識它們全部並不是重點,真正的重點在於,如果你是從其他弱型別、動態定型,或者是型態系統上要求較寬鬆的語言,進入 到 Haskell,必須得重新思考一下,型態對你而言到底是什麼意義?我曾經在 Ruby Conference Taiwan 2014 的〈Understanding Typing. Understanding Ruby.〉做過一些探討。
當你進入到 Haskell 之中,你會發現一件事「開發者對型態的思考總是不足的」,在這篇中認真地重新思考一下型態,之後繼續在 Haskell 中繼續前進時,才不至於處處碰壁。

