如何在Haskell的子类定义中定义默认实现?

如何在Haskell的子类定义中定义默认实现?,haskell,Haskell,我是Haskell的新来者,以下是我的问题: 鉴于这一类别: class MyClass a where foo :: a -> [a] 然后我有一个更具体的子类: class (MyClass a) => SubClass a where foo param = [bar param] bar :: a -> a 但它并没有像预期的那样起作用。我希望在子类的定义中设置一个默认实现,但它没有。我需要分别定义MyClass的实例,但这听起来很愚蠢。当我

我是Haskell的新来者,以下是我的问题:

鉴于这一类别:

class MyClass a where
    foo :: a -> [a]
然后我有一个更具体的子类:

class (MyClass a) => SubClass a where
    foo param = [bar param]
    bar :: a -> a
但它并没有像预期的那样起作用。我希望在
子类的定义中设置一个默认实现,但它没有。我需要分别定义
MyClass
的实例,但这听起来很愚蠢。当我知道某个子类确实满足某个属性时,如何实现默认实现

更具体地说,我想用Haskell表示,
当一个类满足某些属性时,它的父类的某些函数可以有默认实现。在我的示例中,
SubClass
具有属性
bar
,因此我知道
foo
肯定是以这种方式定义的

这个问题的一个更一般的形式是,使用类和实例进行重用是一个好主意吗

我发现这个帖子:


这很接近,但仍然没有完全回答我的问题,而且它们的形式略有不同。

这可以通过
DefaultSignatures
实现:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    foo :: a -> [a]
    default foo :: SubClass a => a -> [a]
    foo param = [param]

class MyClass a => SubClass a where
    bar :: a -> a
在ghci中测试它:

> instance MyClass Integer; instance SubClass Integer where bar = id
> foo 3
[3]

详细说明丹尼尔的回答:

假设您有一个新的数据类型
MyData
,定义为

data MyData = D1 | D2
您想使
MyData
成为
子类的一个实例。你先试试显而易见的解决办法

instance SubClass MyData where
    bar x = case x of { D1 -> D2 ; D2 -> D1 }
不过,对类型签名的快速检查表明,这是行不通的,因为类型必须是
MyClass
的实例,才能成为
子类的实例。因此,您将
MyData
作为
MyClass
的一个实例

instance MyClass MyData
同样,这会引起一个错误,因为
foo
包含在
MyClass
的最小完整定义中。为了使您的实例工作,您必须手动定义
foo
,因此无法达到默认声明的目的

简言之,在basic haskell 98(或haskell 2010)中无法做到这一点。不过,值得庆幸的是,GHC提供了一个名为
DefaultSignatures
的有用扩展。以丹尼尔为例:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    foo :: a -> [a]
    default foo :: SubClass a => a -> [a]
    foo param = [param]

class MyClass a => SubClass a where
    bar :: a -> a

现在,您可以定义实例,它们将按照预期工作。此解决方案的缺点是必须在
MyClass
中定义默认定义,但这是必需的。
foo
的定义属于
MyClass
的定义(或其一个实例声明),因此如果您能够在
子类的定义中定义
foo
的默认声明,Haskell的类型隔离将被打破

foo
的实现需要附加到
foo
所属的类。如果
MyClass
的第二个子类也试图像这样定义
foo
的默认实现,您会期望发生什么?@ReidBarton我想表达的是,foo的
实现在MyClass上是未知的,但鉴于子类的某些属性,可以决定foo的默认实现
。然后查看Daniel Wagner的答案,但请注意,在定义
MyClass
@ReidBarton yeah时,必须选择子类
子类
是什么。我读过。这不是最可取的。难道不能定义一个带有约束的实例来处理这个问题吗?这很好,但我希望默认实现出现在
MyClass
中。通过定义一个带有约束的实例可以实现吗?比如
instance(子类a)=>MyClass a
,但它看起来很奇怪。@hustmphrr我不确定我是否理解你的反对意见。你能进一步澄清吗?特别是,您说您希望默认实现出现在
MyClass
中;但事实上它确实出现在那里。有什么问题吗?噢,我的脑子糟透了。我的意思是出现在
子类
中,因为默认实现属于
子类
@hustmphrr假设在您提议的世界中,我编写了
类MyClass a=>子类1A,其中{foo x=[x]};类MyClass a=>子类2A,其中{foo x=[x,x]};实例子类1整数;实例子类2整数
。在那个世界上,
[3]
[3,3]
的意思是什么?我明白你的意思。但是默认签名不会有同样的问题吗?谢谢你的详细说明。现在很清楚了。但是,为所有
子类定义
MyClass
的实例怎么样?类似于
实例(子类a)=>MyClass a
?是什么阻止了我这么做?@hustmphrr:这样的声明将无法通过歧义检查。只有当占位符的类型可以从上下文推断时,带有不明确类型声明的类型声明(具有
a
等类型的声明)才起作用。例如,如果您有一个签名为
[a]->Int
的函数,然后用
['c'、'$'、'\n']
调用它,它可以推断
a
的类型为
Char
,因此成功。在您的示例中,没有上下文,因此无法确定
a
的类型。这与类型为
[a]
的列表必须仍然是同构的原因相同。