【问题标题】:How to define default implementation in subclass definition in Haskell?如何在 Haskell 的子类定义中定义默认实现?
【发布时间】:2015-07-24 09:31:48
【问题描述】:

我是 Haskell 的新人,以下是我的问题:

给定这个类:

class MyClass a where
    foo :: a -> [a]

然后我有一个更具体的子类:

class (MyClass a) => SubClass a where
    foo param = [bar param]
    bar :: a -> a

但它没有按预期工作。我期待在SubClass 的定义中设置默认实现,但事实并非如此。我需要单独为MyClass 定义实例,但这听起来很愚蠢。当我知道某些子类肯定满足某些属性时,如何实现默认实现?

更具体地说,我想用 Haskell 表达 when a class satisfies some properties, some functions for its parent can have default implementation。在我的示例中,SubClass 具有属性 bar,因此我知道 foo 肯定是以这种方式定义的。

这个问题的一个更一般的形式是,通过使用类和实例来重用是一个好主意吗?

我发现了这个帖子:Inclusion of typeclasses with default implementation in Haskell

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

【问题讨论】:

  • foo 的实现需要附加到foo 所属的类。如果MyClass 的第二个子类也尝试像这样定义foo 的默认实现,您会期望发生什么?
  • @ReidBarton 我想表达implementation of foo is unknown at the point of MyClass but given some property of SubClass, the default implementation of foo can be decided
  • 然后看 Daniel Wagner 的回答,但是注意当你定义 MyClass 时,你必须选择子类 SubClass 是什么。
  • @ReidBarton 是的。我读过那个。这不是最可取的。难道不能定义一个有约束的实例来处理吗?

标签: haskell


【解决方案1】:

详细阐述丹尼尔的回答:

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

data MyData = D1 | D2

您想让MyData 成为SubClass 的实例。你先尝试显而易见的解决方案。

instance SubClass MyData where
    bar x = case x of { D1 -> D2 ; D2 -> D1 }

不过,对类型签名的快速检查表明这是行不通的,因为类型必须是 MyClass 的实例,然后才能成为 SubClass 的实例。所以你让MyData 成为MyClass 的一个实例。

instance MyClass MyData

同样,这会引发错误,因为foo 包含在MyClass 的最小完整定义中。为了使您的实例正常工作,您必须手动定义 foo,从而违背了默认声明的目的。

简而言之,在基本的 Haskell98(或 Haskell2010,就此而言)没有办法做到这一点。然而,值得庆幸的是,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 的定义(或其实例声明之一),因此如果您能够在 SubClass 的定义中定义 foo 的默认声明,Haskell 的 类型隔离 会被打破。

【讨论】:

  • 感谢您的详细说明。现在很清楚了。但是,如何为所有SubClass 定义一个MyClass 实例呢?类似instance (SubClass a) => MyClass a?是什么阻止我这样做?
  • @HuStmpHrrr :这样的声明会使歧义检查失败。只有当占位符的类型可以从上下文中推断出来时,具有模糊类型声明的类型声明(具有类似a 的类型)才有效。例如,如果你有一个签名为[a] -> Int的函数,然后用['c','$','\n']调用它,它可以推断a的类型为Char,所以它成功了。在您的示例中,没有上下文可言,因此无法确定a 的类型。这与[a] 类型的列表仍然必须是同构的原因相同。
【解决方案2】:

这可以通过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]

【讨论】:

  • 这很好,但我希望默认实现与MyClass 一起出现。这可以通过定义一个带有约束的实例来实现吗?像instance (SubClass a) => MyClass a,但看起来很奇怪。
  • @HuStmpHrrr 我不确定我是否理解您的反对意见。你能进一步澄清吗?特别是,您说您希望默认实现出现在MyClass;但它确实出现在那里。那么有什么问题呢?
  • 哦,我的脑子坏掉了。我的意思是出现SubClass,因为默认实现属于SubClass
  • @HuStmpHrrr 假设在你提议的世界中我写了class MyClass a => SubClass1 a where {foo x = [x]}; class MyClass a => SubClass2 a where {foo x = [x,x]}; instance SubClass1 Integer; instance SubClass2 Integerfoo 3 在那个世界里,[3][3,3] 是什么意思?
  • 我明白你的意思。但是默认签名不会有同样的问题吗?
猜你喜欢
  • 2016-06-29
  • 2011-03-17
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 2021-03-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多