Haskell 子类化和实例重叠
Haskell subclassing and instance overlap
来自 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
。由于此扩展现已弃用,取而代之的是每个实例的编译指示,我向子类实例添加了一个 INCOHERENT
编译指示:
instance {-# INCOHERENT #-} (Ord a, OrderedCollection c a) => Collection c a where
...
编译成功。然而,结果不是我所期望的,因为输出是:
ordered elements: [1,2,3]
elements: [1,2,3]
我想要的是 SortedList
的 Collection
的专门实现来覆盖默认值(在 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
隐含)或必须使用不连贯的实例并不感到兴奋。
有趣的是,如果我将 INCOHERENT
pragma 更改为 OVERLAPPABLE
,它仍然可以编译,并且还允许我删除 MonoLocalBinds
.
我的问题是,是否有任何其他方法可以在此处实现所需的“继承”行为,而不需要 test
中的冗余约束?
当你这样写的时候:
instance ... => Collection c a where
您正在为 所有类型 声明一个 Collection
实例。粗箭头 =>
左边的内容根本无关紧要。约束不参与实例解析。当编译器尝试查找特定类型的实例时,它只会查看粗箭头 =>
右侧的内容,只有在找到匹配的实例后,它才会检查其约束是否得到满足。如果不是,编译器将不会返回寻找另一个实例。这就是实例解析的工作原理,并且有充分的理由。
所以,重申一下:Collection c a
意味着这是 所有类型 .
的实例
因此,您可能声明的任何后续 Collection
个实例当然会重叠。
值得庆幸的是,在这种特殊情况下,有一个更好的方法:您可以声明默认方法而无需创建这样的通用实例。为此,请在 class 声明中声明该方法。是的,您也可以对其施加约束(参见 docs):
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
但更一般地说,虽然类型 classes 加上存在量化在技术上等同于 OOP-style class 层次结构,但如果您尝试像那样实际建模您的域,它将是你走得越远,就越尴尬和痛苦。这有点像在 Java 中尝试对 ADT 进行建模。技术上可行,但是太乱了!
在一些合理的情况下,class 层次结构可能有意义(一个值得注意的例子是 GHC 异常系统),但大多数时候有很多更简单的方法。
来自 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
。由于此扩展现已弃用,取而代之的是每个实例的编译指示,我向子类实例添加了一个 INCOHERENT
编译指示:
instance {-# INCOHERENT #-} (Ord a, OrderedCollection c a) => Collection c a where
...
编译成功。然而,结果不是我所期望的,因为输出是:
ordered elements: [1,2,3]
elements: [1,2,3]
我想要的是 SortedList
的 Collection
的专门实现来覆盖默认值(在 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
隐含)或必须使用不连贯的实例并不感到兴奋。
有趣的是,如果我将 INCOHERENT
pragma 更改为 OVERLAPPABLE
,它仍然可以编译,并且还允许我删除 MonoLocalBinds
.
我的问题是,是否有任何其他方法可以在此处实现所需的“继承”行为,而不需要 test
中的冗余约束?
当你这样写的时候:
instance ... => Collection c a where
您正在为 所有类型 声明一个 Collection
实例。粗箭头 =>
左边的内容根本无关紧要。约束不参与实例解析。当编译器尝试查找特定类型的实例时,它只会查看粗箭头 =>
右侧的内容,只有在找到匹配的实例后,它才会检查其约束是否得到满足。如果不是,编译器将不会返回寻找另一个实例。这就是实例解析的工作原理,并且有充分的理由。
所以,重申一下:Collection c a
意味着这是 所有类型 .
因此,您可能声明的任何后续 Collection
个实例当然会重叠。
值得庆幸的是,在这种特殊情况下,有一个更好的方法:您可以声明默认方法而无需创建这样的通用实例。为此,请在 class 声明中声明该方法。是的,您也可以对其施加约束(参见 docs):
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
但更一般地说,虽然类型 classes 加上存在量化在技术上等同于 OOP-style class 层次结构,但如果您尝试像那样实际建模您的域,它将是你走得越远,就越尴尬和痛苦。这有点像在 Java 中尝试对 ADT 进行建模。技术上可行,但是太乱了!
在一些合理的情况下,class 层次结构可能有意义(一个值得注意的例子是 GHC 异常系统),但大多数时候有很多更简单的方法。