在Haskell中,为什么会有一个类型类hierarchy/inheritance?

In Haskell, why is there a typeclass hierarchy/inheritance?

为了澄清我的问题,让我用大致相同的方式重新表述一下:

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

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

我不明白为什么 class 类型继承存在于 Haskell 中。我觉得它有点奇怪,因为它在概念上造成了不对称。 通常在数学中,概念可以从不同的角度来定义,我不一定要赞成它们应该如何定义的顺序。好吧,人们应该按照某种顺序来证明事情,但是一旦有了定理和结构,我宁愿将它们视为可用的独立工具。

此外,我在 class 继承中看到的一件可能不太好的事情是:我认为 class 实例会默默地选择相应的 superclass 实例,这可能已实现成为该类型最自然的人。让我们考虑将 Monad 视为 Functor 的子class。也许有不止一种方法可以在某种类型上定义 Functor,而这种类型恰好也是 Monad。但是说一个 Monad 是一个 Functor 隐含地为那个 Monad 选择了一个特定的 Functor。有一天,您可能会忘记实际上您想要一些其他的 Functor。 也许这个例子不是最合适的,但我觉得如果您的 class 是许多 child 中的一个,这种情况可能会普遍存在并且可能很危险。当前的 Haskell 继承听起来像是隐式地对 parents 进行了默认选择。

相反,如果您有一个没有层次结构的设计,我觉得您将始终必须明确说明所需的所有属性,这可能意味着风险更小、更清晰、更对称。到目前为止,我所看到的是这种设计的成本是:在实例定义和新类型包装器中编写更多约束,用于从一组概念到另一组概念的每次有意义的转换。我不确定,但也许这是可以接受的。不幸的是,我认为 Haskell 新类型的自动派生机制不能很好地工作,我希望这种语言在某种程度上更智能地使用新类型 wrapping/unwrapping 并且需要更少的冗长。 我不确定,但现在我想起来了,也许新类型包装器的替代方法是特定导入包含特定实例变体的模块。

我在写这篇文章时想到的另一种选择是,也许可以削弱 class (P x) => C x 的含义,而不是要求 C 的实例选择 P,我们可以粗略地理解为,例如,C class 也包含 P 的方法,但没有自动选择 P 的实例,不存在与 P 的其他关系。所以我们可以保留某种可能更灵活的较弱的层次结构。

谢谢,如果你对那个话题有一些澄清,and/or纠正我可能的误解。

也许你已经厌倦了我的消息,但是这里...

我认为 superclasses 是作为 classes 类型的一个相对次要且不重要的特性引入的。在 Wadler and Blott, 1988 中,第 6 节给出了示例 class Eq a => Num a,对它们进行了简要讨论。在那里,提供的唯一理由是必须在函数类型中编写 (Eq a, Num a) => ... 很烦人,而它应该是 "obvious" 可以添加、乘法和取反的数据类型应该是可测试的相等性以及。 superclass 关系允许 "a convenient abbreviation".

(这个例子是如此糟糕这一事实强调了这个特征的不重要性。现代 Haskell 没有 class Eq a => Num a 因为所有 Num 的逻辑理由也是 Eqs 太弱了。例子 class Eq a => Ord a 会更有说服力。)

因此,在没有任何超classes 的情况下实现的基础库看起来或多或少是一样的。库和用户代码中的函数类型签名在逻辑上只会有更多多余的约束,而不是回答这个问题,我会回答一个关于为什么的初学者问题:

leq :: (Ord a) => a -> a -> Bool
leq x y = x < y || x == y

不检查类型。

就您关于 superclasses 强制特定层次结构的观点而言,您没有达到目标。

这种"forcing"实际上是classes类型的基本特征。类型 class 是 "opinionated by design",在给定的 Haskell 程序中(其中 "program" 包括所有库,包括程序使用的 base),可以只是特定类型的特定类型 class 的一个实例。这 属性 被称为连贯性。 (即使有语言扩展 IncohorentInstances,它也被认为是非常危险的,只有当特定类型的所有可能实例 class 对于特定类型在功能上等同时才应使用。)

此设计决策会带来一定的成本,但也会带来许多好处。 Edward Kmett 从 14:25 左右开始在 this video 中详细讨论了这一点。特别是,他比较了 Haskell 的设计一致性类型 classes 与 Scala 的设计不一致的隐含类型,并将 Scala 方法带来的增强功能与可重用性(和重构优势)进行了对比) "dumb data types" 随 Haskell 方法而来。

因此,在设计 space 中有足够的空间用于连贯类型 classes 和非连贯隐式,并且 Haskell 的方法不一定是正确的。

但是,因为Haskell选择了连贯类型classes,没有"cost"具有特定的层次结构:

class Functor a => Monad a

因为,对于特定类型,如 []MyNewMonadDataType,无论如何只能有一个 Monad 和一个 Functor 实例。 superclass 关系引入了一个要求,即任何具有 Monad 实例的类型必须具有 Functor 个实例,但它不限制选择 Functor 个实例 因为你一开始就没有选择。或者更确切地说,您的选择是在零个 Functor [] 个实例和一个实例之间。

请注意,这与 Monad 类型是否只有一个合理的 Functor 实例的问题是分开的。原则上,我们可以定义一个具有不兼容 FunctorMonad 实例的违法数据类型。在我们的程序中,我们仍然只能使用那个 Functor MyType 实例和那个 Monad MyType 实例,无论 Functor 是否是 [=22= 的超级 class ].