Haskell子类和实例重叠
来自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
在包含必要的语言扩展之后,这仍然给了我一个错误:由使用“元素”
引起的集合的重叠实例。它建议使用incherentInstances
。由于现在将此扩展名不推荐使用,因此我在子类实例中添加了一个Incoherent
pragma:
instance {-# INCOHERENT #-} (Ord a, OrderedCollection c a) => Collection c a where
...
成功编译的 pragma。但是,结果不是我所期望的,而是输出是:
ordered elements: [1,2,3]
elements: [1,2,3]
我想要的是用于Collection> for
sortedlist
的专门实现,以覆盖默认值(以OO语言为OO语言,sortedlist
将从orderedCollection
继承,然后覆盖Elements
方法)。但是在这里,类型的检查器不知道使用sortedlist
的自定义collection
实现,因为test> test
的类型签名仅强加约束订购Collection 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
约束(给定droundedcollection c a
),或者必须使用不连贯的实例。
有趣的是,如果我将不连贯的
pragma更改为重叠
,它仍会编译,它也使我可以删除MonoLocalBinds
。
我的问题是,是否有其他方法可以在这里实现所需的“继承”行为,而无需在test
中需要冗余约束?
Coming from the OOP world, I sometimes find myself trying to use the inheritance pattern in Haskell, with varying degrees of success. Here's a little puzzle I encountered with subclassing (using 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
After including the necessary language extensions, this still gave me an error: Overlapping instances for Collection c a arising from a use of ‘elements’
. It suggests using IncoherentInstances
. Since this extension is now deprecated in favor of per-instance pragmas, I added an INCOHERENT
pragma to the subclass instance:
instance {-# INCOHERENT #-} (Ord a, OrderedCollection c a) => Collection c a where
...
This successfully compiled. However, the result was not what I expected, as the output was:
ordered elements: [1,2,3]
elements: [1,2,3]
What I wanted was for the specialized implementation of Collection
for SortedList
to override the default (in an OO language, SortedList
would inherit from OrderedCollection
and then override the elements
method). But here the type checker does not know to use SortedList
's custom Collection
implementation, because the type signature of test
only imposes the constraint OrderedCollection c a
.
Next, I tried adding the Collection
constraint:
test :: (Ord a, Show a, Collection c a, OrderedCollection c a) => c a -> IO ()
This gave me the output I wanted:
ordered elements: [1,2,3]
elements: [3,2,1]
However, GHC also issued a warning about "fragile inner bindings" and suggested I add the MonoLocalBinds
extension, which silences that warning. In any case, I'm not thrilled with having to include the Collection c a
constraint (given it's implied by OrderedCollection c a
), or having to use incoherent instances.
Interestingly, if I changed the INCOHERENT
pragma to OVERLAPPABLE
, it still compiled, and it also allowed me to remove MonoLocalBinds
.
My question is, are there any alternative approaches to achieving the desired "inheritance" behavior here, without needing the redundant constraint in test
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
写这篇文章时:
您正在声明
集合
实例所有类型。而且,脂肪箭头=>
的左侧什么都没有关系。约束不参与实例分辨率。当编译器试图查找特定类型的实例时,它仅查看fat Arrow=>
的右侧的内容,并且只有在找到匹配的实例后,才会检查其约束是否为使满意。如果不是,则编译器将不会回去寻找另一个实例。这就是实例解决方案的工作方式,并且有充分的理由。因此,要重申:
集合C A
表示这是 ach aultess 的实例。因此,您可能声明的任何后续
集合
实例当然都会重叠。值得庆幸的是,在此特殊情况下,有一种更好的方法:您可以在不创建这样的通用实例的情况下声明默认方法。为此,请在类声明中声明该方法。是的,您也可以在上面加上约束(请参阅 docs ):
但是更一般而言,虽然类型类加上存在量化在技术上等同于OOP式类别的层次结构,但如果您尝试实际建模这样的域,但它会越来越尴尬和痛苦进一步。这有点像试图在Java之类的东西中建模ADT。从技术上讲是可能的,但是哦,太混乱了!
在某些合法情况下,类层次结构可能是有道理的(一个值得注意的例子是GHC异常系统),但是大多数时候 都有更简单的方法。
When you write this:
You're declaring a
Collection
instance for all types ever. And it doesn't matter at all what's on the left of the fat arrow=>
. Constraints do not participate in instance resolution. When the compiler tries to lookup an instance for a particular type, it only looks at what's on the right of the fat arrow=>
, and only after finding a matching instance does it check if its constraints are satisfied. And if they're not, the compiler won't go back to look for another instance. That's how instance resolution works, and there are good reasons for it.So, to reiterate:
Collection c a
means that this is an instance for all types.And therefore, any subsequent
Collection
instances you might declare would of course be overlapping.Thankfully, in this particular case, there is a better way: you can declare default methods without creating a universal instance like that. To do that, declare the method right inside the class declaration. And yes, you can put constraints on it too (see docs):
But more generally, while type classes plus existential quantification is technically equivalent to OOP-style class hierarchies, if you try to actually model your domain like that, it would be more and more awkward and painful the further you go. It's a bit like trying to model ADTs in something like Java. Technically possible, but oh so messy!
There are some legitimate cases where a class hierarchy may make sense (one notable example is the GHC exception system), but most of the time there are much simpler ways.