Haskell子类和实例重叠

发布于 2025-01-21 21:38:55 字数 2588 浏览 6 评论 0原文

来自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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深海夜未眠 2025-01-28 21:38:55

写这篇文章时:

instance ... => Collection c a where

您正在声明集合实例所有类型。而且,脂肪箭头=>的左侧什么都没有关系。约束不参与实例分辨率。当编译器试图查找特定类型的实例时,它仅查看fat Arrow =>的右侧的内容,并且只有在找到匹配的实例后,才会检查其约束是否为使满意。如果不是,则编译器将不会回去寻找另一个实例。这就是实例解决方案的工作方式,并且有充分的理由。

因此,要重申:集合C A表示这是 ach aultess 的实例。

因此,您可能声明的任何后续集合实例当然都会重叠。


值得庆幸的是,在此特殊情况下,有一种更好的方法:您可以在不创建这样的通用实例的情况下声明默认方法。为此,请在类声明中声明该方法。是的,您也可以在上面加上约束(请参阅 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

但是更一般而言,虽然类型类加上存在量化在技术上等同于OOP式类别的层次结构,但如果您尝试实际建模这样的域,但它会越来越尴尬和痛苦进一步。这有点像试图在Java之类的东西中建模ADT。从技术上讲是可能的,但是哦,太混乱了!

在某些合法情况下,类层次结构可能是有道理的(一个值得注意的例子是GHC异常系统),但是大多数时候 都有更简单的方法。

When you write this:

instance ... => Collection c a where

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):

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文