在Haskell中,为什么会有类型类层次结构/继承?

时间:2020-05-14 09:22:24

标签: haskell typeclass

为澄清我的问题,让我以或多或少等效的方式重新表述:

为什么在Haskell中有超类/类继承的概念? 导致该设计选择的历史原因是什么? 例如,为什么拥有一个没有类层次结构的基础库,而仅仅是彼此独立的类型类,为什么会如此糟糕呢?

在这里,我将揭露一些使我想问这个问题的随机想法。我当前的直觉可能不准确,因为它们是基于我对Haskell的当前理解,这并不完美,但在这里却是...

对于我来说,为什么Haskell中存在类型类继承并不明显。我发现它有点怪异,因为它在概念上造成了不对称。 通常在数学中,可以从不同的角度定义概念,我并不一定要赞成应该如何定义它们的顺序。好的,有一些顺序可以证明事物,但是一旦有了定理和结构,我宁愿将它们视为可用的独立工具。

此外,我在类继承中看到的可能不是一件好事:我认为一个类实例将默默地选择一个对应的超类实例,该实例可能被实现为对该类型最自然的实例。让我们考虑将Monad视为Functor的子类。也许有多种方法可以在碰巧是Monad的类型上定义Functor。但是,如果说Monad是函子,则意味着要为该Monad选择一个特定的Functor。总有一天,您可能会忘记实际上还需要其他Functor。 也许这个例子不是最合适的,但是我觉得如果您的班级是很多孩子的话,这种情况可能会泛泛化并且可能很危险。当前的Haskell继承听起来像是在隐式地对父母进行默认选择。

如果您的设计没有层次结构,那么我认为您始终必须对所需的所有属性都保持明确,这也许意味着风险更低,更清晰,更对称。到目前为止,我所看到的是,这种设计的成本是:对于从一组概念到另一组概念的每次有意义的转换,要在实例定义和新类型包装程序中写入更多的约束。我不确定,但是也许可以接受。不幸的是,我认为用于新类型的Haskell自动派生机制不能很好地工作,我希望通过使用新类型的包装/展开,该语言在某种程度上更智能,并且所需的冗长程度较低。 我不确定,但是现在我考虑了一下,也许新类型包装的替代方法可能是包含特定实例变体的模块的特定导入。

我在撰写本文时考虑的另一种选择是,可能会削弱class (P x) => C x的含义,而不是要求C的实例选择{{1 }},我们可以简单地说一下,例如P类也包含C的方法,但是没有自动选择P的实例,也没有与{{ 1}}存在。因此,我们可以保留一些较弱的层次结构,该层次结构可能更灵活。

如果您对此主题有一些澄清,和/或纠正我可能的误解,谢谢。

1 个答案:

答案 0 :(得分:2)

也许您已经厌倦了听我的话,但是这里...

我认为,超类是作为类型类的相对次要且不重要的功能引入的。在Wadler and Blott, 1988中,将在第6节中简要讨论它们,并给出示例class Eq a => Num a。在那里,提供的唯一理由是,当必须“明显”地证明可以添加,乘法和取反的数据类型也应该可以测试相等性时,必须在函数类型中编写(Eq a, Num a) => ...很烦人。超类关系允许“方便的缩写”。

(此示例如此糟糕,凸显了此功能的重要性。现代Haskell没有class Eq a => Num a,因为所有Num的逻辑理由也为{{1} } s太弱了。示例Eq更具说服力。)

因此,没有任何超类实现的基本库看起来或多或少都是相同的。在库和用户代码中,对函数类型签名的约束在逻辑上都是多余的,而不是提出这个问题,我将提出关于以下原因的初学者问题:

class Eq a => Ord a

不键入检查。

关于强制执行特定层次结构的超类的观点,您缺少目标。

这种“强制”实际上是类型类的基本特征。类型类是“由设计决定的”,并且在给定的Haskell程序(其中“程序”包括所有库,包括程序使用的leq :: (Ord a) => a -> a -> Bool leq x y = x < y || x == y )中,对于一个特定类型类,只能有一个实例。特定类型。此属性称为一致性。 (即使有语言扩展名base,也被认为是非常危险的,只有在特定类型的特定类型类的所有可能实例在功能上等效时,才应使用它。)

此设计决定会带来某些成本,但也会带来许多好处。爱德华·克梅特(Edward Kmett)在this video中详细讨论了这一点,大约从14:25开始。特别是,他将Haskell的按设计的一致性类型类与Scala的按设计的非一致性隐式类进行了比较,并对比了Scala方法带来的强大功能与随代码提供的“哑数据类型”的可重用性(以及重构优势)。 Haskell方法。

因此,在设计空间中,对于连贯类型类和不连贯隐式而言,都有足够的空间,而Haskell的方法不一定是正确的方法。

,因为Haskell选择了一致的类型类,所以拥有特定的层次结构没有“成本”:

IncohorentInstances

因为对于一种特定类型,例如class Functor a => Monad a [],无论如何只能有一个MyNewMonadDataType和一个Monad实例。超类关系引入了一个要求,即具有Functor实例的任何类型都必须具有Monad实例,但是它不会限制Functor实例的选择,因为您从没有选择权第一名。或者,您的选择是在拥有零个Functor实例和正好有一个实例之间。

请注意,这与Functor []类型是否只有一个合理的Functor实例这一问题是分开的。原则上,我们可以使用不兼容的MonadFunctor实例定义一个违反法律的数据类型。无论程序Monad是否是Functor MyType的超类,我们仍然被限制在整个程序中使用那个Monad MyType实例和那个Functor实例。