跳轉至內容

Haskell/高階型別類

來自 Wikibooks,開放世界中的開放書籍

型別類可能看起來無害,但對該主題的研究已經導致了一些進步和概括,這些進步和概括使它們成為一個非常強大的工具。

多引數型別類

[編輯 | 編輯原始碼]

多引數型別類是對單引數型別類的泛化,並由一些 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。如果 cHashmap 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。並且你可以在多引數類中擁有兩個以上的引數。

矩陣和向量

[編輯 | 編輯原始碼]

假設你想實現一些程式碼來執行簡單的線性代數

示例:VectorMatrix 資料型別

 data Vector = Vector Int Int deriving (Eq, Show)
 data Matrix = Matrix Vector Vector deriving (Eq, Show)

你希望它們的行為儘可能像數字。因此,你可能會從過載 Haskell 的 Num 類開始

示例:VectorMatrix 的例項宣告

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 cab 唯一確定。


華夏公益教科書