Haskell/高階型別類
型別類可能看起來無害,但對該主題的研究已經導致了一些進步和概括,這些進步和概括使它們成為一個非常強大的工具。
多引數型別類是對單引數型別類的泛化,並由一些 Haskell 實現支援。
假設我們想建立一個“集合”型別類,它可以與各種具體的資料型別一起使用,並支援兩個操作——“插入”用於新增元素,以及“成員”用於測試成員資格。第一次嘗試可能看起來像這樣
示例:Collection 型別類(錯誤)
class Collection c where
insert :: c -> e -> c
member :: c -> e -> Bool
-- Make lists an instance of Collection:
instance Collection [a] where
-- insert :: [a] -> e -> [a]
insert xs x = x:xs
-- member :: [a] -> e -> Bool
member = flip elem
然而,這將無法編譯,因為 (:) 的引數必須是 [a] 和 a 型別,而我們提供的是 e。問題是 Collection 操作中的 'e' 型別變數來自無處——Collection 例項的型別中沒有任何內容可以告訴我們 'e' 到底是什麼,因此我們永遠無法定義這些方法的實現。多引數型別類透過允許我們將 'e' 放入類的型別來解決這個問題。這是一個可以編譯並使用的示例
示例:Collection 型別類(正確)
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
class Eq e => Collection c e where
insert :: c -> e -> c
member :: c -> e -> Bool
instance Eq a => Collection [a] a where
insert = flip (:)
member = flip elem
上面示例中的一個問題是,在這種情況下,我們擁有編譯器不知道的額外資訊,這會導致錯誤的歧義和過於泛化的函式簽名。在這種情況下,我們可以直觀地看到集合的型別將始終決定它包含的元素的型別——所以如果 c 是 [a],那麼 e 將是 a。如果 c 是 Hashmap a,那麼 e 將是 a。(反之則不然:許多不同的集合型別可以儲存相同的元素型別,因此知道元素型別是例如 Int,不會告訴你集合型別)。
為了告訴編譯器這些資訊,我們新增一個函式依賴,將類宣告更改為
示例:函式依賴
{-# LANGUAGE FunctionalDependencies #-}
class Eq e => Collection c e | c -> e where ...
函式依賴是我們可以對型別類引數施加的約束。這裡,額外的 | c -> e 應該讀作“c 唯一標識 e”,意味著對於給定的 c,將只有一個 e。你可以在一個類中擁有多個函式依賴——例如,在上述情況下,你可以擁有 c -> e, e -> c。並且你可以在多引數類中擁有兩個以上的引數。
假設你想實現一些程式碼來執行簡單的線性代數
示例:Vector 和 Matrix 資料型別
data Vector = Vector Int Int deriving (Eq, Show) data Matrix = Matrix Vector Vector deriving (Eq, Show)
你希望它們的行為儘可能像數字。因此,你可能會從過載 Haskell 的 Num 類開始
示例:Vector 和 Matrix 的例項宣告
instance Num Vector where
Vector a1 b1 + Vector a2 b2 = Vector (a1+a2) (b1+b2)
Vector a1 b1 - Vector a2 b2 = Vector (a1-a2) (b1-b2)
{- ... and so on ... -}
instance Num Matrix where
Matrix a1 b1 + Matrix a2 b2 = Matrix (a1+a2) (b1+b2)
Matrix a1 b1 - Matrix a2 b2 = Matrix (a1-a2) (b1-b2)
{- ... and so on ... -}
問題出現在你想要開始將數量相乘時。你真的需要一個過載到不同型別的乘法函式
示例:我們需要什麼
(*) :: Matrix -> Matrix -> Matrix
(*) :: Matrix -> Vector -> Vector
(*) :: Matrix -> Int -> Matrix
(*) :: Int -> Matrix -> Matrix
{- ... and so on ... -}
我們如何指定一個型別類來允許所有這些可能性?
我們可以嘗試這個
示例:無效的嘗試(過於泛化)
class Mult a b c where
(*) :: a -> b -> c
instance Mult Matrix Matrix Matrix where
{- ... -}
instance Mult Matrix Vector Vector where
{- ... -}
然而,那並不是我們真正想要的。就目前而言,即使是像這樣的簡單表示式也具有模糊的型別,除非你在中間表示式上提供額外的型別宣告
示例:歧義導致程式碼更冗長
m1, m2, m3 :: Matrix (m1 * m2) * m3 -- type error; type of (m1*m2) is ambiguous (m1 * m2) :: Matrix * m3 -- this is ok
畢竟,沒有任何東西能阻止別人後來新增另一個例項
示例:Mult 的無意義例項
instance Mult Matrix Matrix (Maybe Char) where
{- whatever -}
問題是 c 不應該是一個自由型別變數。當你瞭解你正在相乘的事物的型別時,結果型別應該僅從此資訊中確定。
你可以透過指定函式依賴來表達這一點
示例:Mult 的正確定義
class Mult a b c | a b -> c where (*) :: a -> b -> c
這告訴 Haskell c 由 a 和 b 唯一確定。
至少本頁的部分內容是從 Haskell wiki 文章 函式依賴 匯入的,根據其簡單許可證。如果你希望修改此頁面,並且你的更改對該 wiki 也有用,你可以考慮修改該源頁面而不是此頁面,因為來自該頁面的更改可能會傳播到這裡,反之則不然。或者,你可以明確地將你的貢獻雙重授權在簡單許可證下。 |