來自 OOP 世界,我有時會發現自己嘗試使用 Haskell 中的繼承模式,并取得了不同程度的成功。這是我在子類化中遇到的一個小難題(使用 GHC 8.10.7)。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.List (sort)
class Collection c a where
-- gets list of elements in the collection
elements :: c a -> [a]
class OrderedCollection c a where
-- gets sorted list of elements in the collection
orderedElements :: c a -> [a]
instance (Ord a, OrderedCollection c a) => Collection c a where
-- "default" implementation
elements = orderedElements
newtype SortedList a = SortedList [a]
deriving Show
instance (Ord a) => OrderedCollection SortedList a where
-- need to sort the elements in the list
orderedElements (SortedList xs) = sort xs
instance Collection SortedList a where
-- "optimized" implementation: no need to sort
elements (SortedList xs) = xs
test :: (Ord a, Show a, OrderedCollection c a) => c a -> IO ()
test coll = do
putStrLn $ "ordered elements: " show (orderedElements coll)
putStrLn $ "elements: " show (elements coll)
myList :: SortedList Int
myList = SortedList [3, 2, 1]
main :: IO ()
main = do
test myList
包括必要的語言擴展后,這仍然給了我一個錯誤:Overlapping instances for Collection c a arising from a use of ‘elements’. 它建議使用IncoherentInstances. 由于現在不推薦使用此擴展以支持 per-instance pragma,因此我向INCOHERENT子類實體添加了一個 pragma:
instance {-# INCOHERENT #-} (Ord a, OrderedCollection c a) => Collection c a where
...
這樣編譯成功。但是,結果不是我所期望的,因為輸出是:
ordered elements: [1,2,3]
elements: [1,2,3]
我想要的是 for 的專門實作Collection覆寫SortedList默認值(在 OO 語言中,SortedList將繼承OrderedCollection然后覆寫該elements方法)。但是這里的型別檢查器不知道使用SortedList的自定義Collection實作,因為 的型別簽名test只施加了約束OrderedCollection c a。
接下來,我嘗試添加Collection約束:
test :: (Ord a, Show a, Collection c a, OrderedCollection c a) => c a -> IO ()
這給了我想要的輸出:
ordered elements: [1,2,3]
elements: [3,2,1]
然而,GHC 也發出了關于“脆弱的內部系結”的警告,并建議我添加MonoLocalBinds擴展名,從而使該警告靜音。在任何情況下,我都不會因為必須包含Collection c a約束(假設它由 暗示OrderedCollection c a)或必須使用不連貫的實體而感到興奮。
有趣的是,如果我將INCOHERENTpragma 更改為OVERLAPPABLE,它仍然可以編譯,并且還允許我洗掉MonoLocalBinds.
我的問題是,是否有任何替代方法可以在這里實作所需的“繼承”行為,而不需要多余的約束test?
uj5u.com熱心網友回復:
當你寫這個:
instance ... => Collection c a where
您正在為所有型別Collection宣告一個實體。胖箭頭左邊是什么都沒有關系。約束不參與實體決議。當編譯器嘗試查找特定型別的實體時,它只會查看粗箭頭右側的內容,并且只有在找到匹配的實體后才會檢查其約束是否得到滿足。如果不是,編譯器就不會回去尋找另一個實體。這就是實體決議的作業原理,并且有充分的理由。=>=>
所以,重申一下:Collection c a意味著這是所有型別的實體。
因此,Collection您可能宣告的任何后續實體當然會重疊。
值得慶幸的是,在這種特殊情況下,有一種更好的方法:您可以宣告默認方法,而無需創建這樣的通用實體。為此,請在類宣告中宣告該方法。是的,您也可以對其施加限制(請參閱檔案):
class Collection c a where
-- gets list of elements in the collection
elements :: c a -> [a]
default elements :: OrderedCollection c a => c a -> [a]
elements = orderedElements
但更一般地說,雖然型別類加上存在量化在技術上等同于 OOP 風格的類層次結構,但如果您嘗試像那樣實際建模您的域,那么您越走越尷尬和痛苦。這有點像嘗試用 Java 之類的東西對 ADT 建模。技術上可行,但是太混亂了!
在某些合理的情況下,類層次結構可能有意義(一個值得注意的例子是 GHC 例外系統),但大多數時候有更簡單的方法。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/459970.html
