假設你想定義一個 Rectangle
型態,可定義左上原點座標與寬長:
data Rectangle = Rect Int Int Int Int deriving Show
嗯?四個 Int
中哪些是原點 x、y 座標?哪些又是寬高 width、height 呢?也許你可以建立一些函式來解決這個問題:
xOf (Rect x _ _ _) = x
yOf (Rect _ y _ _) = y
widthOf (Rect _ _ w _) = w
heightOf (Rect _ _ _ h) = h
這麼一來,如果有個 Rectangle
型態的值 rect
,就可以分別使用 xOf rect
、widthOf rect
等來取得座標或寬長等值,不過,在建立 Rectangle
的值時,你還是得記得各項的順序,像是 Rect 10 5 20 30
這樣的順序,並不是很方便。
使用 Record 記錄各項名稱
Haskell 提供了一個 Record 語法,可以讓你指定各項名稱:
data Rectangle = Rect { x :: Int
, y :: Int
, width :: Int
, height :: Int } deriving Show
這麼做的好處有幾個,從型態定義上,可清楚地知道各項之意義,如果你使用 deriving
自動衍生自 Show
,那麼產生的字串描述中,也會包括 Record 語法中指定的各項名稱:
而且,現在建構 Rectangle
的值時,不一定得按照順序了,你可以指定項目名稱的值各是為何就可以了:
實際上,Haskell 會使用你指定的項目名稱,產生各個函式,例如,這邊就產生了 x
、y
、width
、height
四個函式,可以讓你指定 Rectangle
的值,分別當中取得各項的值:
你還是可以使用 Rect 10 20 15 30
的方式來建立 Rectangle
的值,因為值構造式是個函式,也可以使用 Rect 10 20
這樣的方式來做部份套用,不過,使用 Record 語法建立值時,每個項都必須指定。
建立 Type 同義詞
如果你寫了個 allToUpper
函式,可以將指定的小寫字串清單,全部轉為大寫的字串清單:
import Data.Char
allToUpper :: [[Char]] -> [[Char]]
allToUpper xs = [map toUpper x | x <- xs]
在之前就談過,字串實際上是字元清單,因此對於一個字串清單,它的型態其實是 [[Char]]
,所以在 allToUpper
的函式宣告上,可以看到 [[Char]] -> [[Char]]
,這並不好閱讀,你可以改定義為:
import Data.Char
allToUpper :: [String] -> [String]
allToUpper xs = [map toUpper x | x <- xs]
這顯然容易閱讀的多,可以這麼定義的原因在於,Haskell 使用了 type
關鍵字定義了 String
為 [Char]
的同義詞:
type String = [Char]
如果使用了同義詞來定義函式型態,使用 :t
來測試 allToUpper
時,結果會顯示 allToUpper :: [String] -> [String]
而不是一開始看到的 allToUpper :: [[Char]] -> [[Char]]
。
你可以為任何具體型態定義同義詞,例如,若有個函式可接受 URL 對應清單進行處理,像是 [("GET /books", "books/index"), ("POST /books", "/books/create")]
,那麼函式型態宣告時的參數定義會像是 [([Char], [Char])] -> SomeType
,你可以改為 [(String, String)]
,或者進一步定義同義詞:
type UrlMappingLt = [([Char], [Char])]
這麼一來,你的函式型態宣告就會像是 UrlMappingLt -> SomeType
,較為簡潔一些。
具型態參數的同義詞
除了為具體型態建立同義詞之外,定義同義詞時也可以有型態參數,還記得〈Haskell Tutorial(14)減輕型態負擔的型態參數〉的最後,我出了一個題目嗎?不知道你有沒有完成?可以實作的方式之一是:
data Map k v = Empty | Cm (k, v) (Map k v)
fromList :: [(k, v)] -> Map k v
fromList [] = Empty
fromList (x:xs) = Cm x (fromList xs)
findValue :: (Eq k) => k -> Map k v -> Maybe v
findValue key Empty = Nothing
findValue key (Cm (k, v) xm) =
if key == k then Just v else findValue key xm
顯然地,這只是在模仿 List 的定義,並不是實際上 Haskell 中 Data.Map
模組中的定義,這麼實作是過份簡化了,不過作為一個練習是夠了。
在上頭的練習中,fromList
型態宣告是 [(k, v)] -> Map k v
,如果你定義:
type PairLt k v = [(k, v)]
那麼 fromList
的函式宣告,就可以改為 PairLt k v -> Map k v
:
fromList :: PairLt k v -> Map k v
fromList [] = Empty
fromList (x:xs) = Cm x (fromList xs)
你也可以在定義同義詞時,部份套用型態參數,例如,也許有某個函式:
lookUpByName :: [Char] -> [([Char], v)] -> Maybe v
lookUpByName n ((name, value):xs) =
if n == name then Just value else lookUpByName n xs
這種情況下透過同義詞,可以讓 [Char] -> [([Char], v)] -> Maybe v
變得簡潔一些:
type StringKeyPairLt v = [([Char], v)]
lookUpByName :: String -> StringKeyPairLt v -> Maybe v
lookUpByName n ((name, value):xs) =
if n == name then Just value else lookUpByName n xs
那麼,如果定義了同義詞,想要知道它原等同哪個型態定義怎麼辦?你可以使用 :info
來得知: