【发布时间】:2020-08-30 18:35:18
【问题描述】:
为了澄清我的问题,让我以或多或少等效的方式重新表述它:
为什么在 Haskell 中有超类/类继承的概念? 导致这种设计选择的历史原因是什么? 例如,拥有一个没有类层次结构、只有相互独立的类型类的基础库,为什么会如此糟糕?
在这里,我将揭露一些让我想问这个问题的随机想法。我目前的直觉可能是不准确的,因为它们是基于我目前对 Haskell 的理解,这并不完美,但它们在这里......
对我来说,为什么在 Haskell 中存在类型类继承并不明显。我觉得这有点奇怪,因为它在概念上造成了不对称。 通常在数学中,可以从不同的角度定义概念,我不一定要支持应该如何定义它们的顺序。好吧,人们应该按照某种顺序证明事物,但是一旦定理和结构出现,我宁愿将它们视为可用的独立工具。
此外,我在类继承中看到的一件可能不太好的事情是:我认为一个类实例会默默地选择一个相应的超类实例,它可能被实现为该类型最自然的实例。让我们考虑一个被视为 Functor 子类的 Monad。也许有不止一种方法可以在某种类型上定义 Functor,而这种类型也恰好是 Monad。但是说一个 Monad 是一个 Functor 隐含地为那个 Monad 选择了一个特定的 Functor。有一天,你可能会忘记实际上你想要一些其他的 Functor。 也许这个例子不是最合适的,但我觉得这种情况可能会泛化,如果你的班级是许多孩子的孩子,可能会很危险。当前的 Haskell 继承听起来像是隐式地对父母进行了默认选择。
如果您的设计没有层次结构,我觉得您总是必须明确所有所需的属性,这可能意味着风险更小、更清晰、更对称。到目前为止,我所看到的是这种设计的成本将是:对于从一组概念到另一组概念的每次有意义的转换,在实例定义和新类型包装器中编写更多的约束。我不确定,但也许这是可以接受的。不幸的是,我认为新类型的 Haskell 自动派生机制不能很好地工作,我希望该语言在新类型包装/展开方面更智能,并且需要更少的冗长。 我不确定,但现在我想一想,也许新类型包装器的替代方案可能是包含特定实例变体的模块的特定导入。
我在写这篇文章时想到的另一种选择是,也许可以削弱class (P x) => C x 的含义,而不是要求C 的实例选择P 的实例,我们可以只粗略地说,例如,C 类也包含P 的方法,但没有自动选择P 的实例,与P 不存在其他关系。所以我们可以保留某种较弱的层次结构,可能更灵活。
感谢您对该主题的一些澄清,和/或纠正我可能的误解。
【问题讨论】:
-
对此我无法给出明确的答案,因此我将发表评论。两点。第一,在许多情况下具有层次结构是有意义的:
Monoid,无论是在数学上还是在 Haskell 中,都必须是Semigroup(你的 Functor 示例也是一个很好的例子)。二、多类型类定义的问题独立于继承而存在。例如,您可以使列表的Applicative实例使用普通的ap和ZipListpure。不幸的是,我们为类定义的定律在标准的 haskell 中不容易证明。然而,违法是继承与否的问题。 -
我不确定我是否理解您声称的某些内容。 Haskell 中的 AFAIU 类型类层次结构表示数学中的约束。 Applicative Functor 必然是数学中的 Functor。所以约束
Functor f => Applicative f正好代表:“只有函子也可以是应用函子”。我也很好奇你说“c 选择了 p 的 an 实例”。没有这样的“选择”。它的意思是“要使 x 成为 C,x 首先需要成为 P”。不涉及选择。 -
But saying that a Monad is a Functor implicitly makes the choice of one particular Functor for that Monad.但这只是对事物现状的一种表达。一旦你有了 Monad,就会有一个且只有一个合理的 Functor 实例,由fmap f x = x >>= (return . f)给出 -
我相信这主要是为了方便。在很久以前,我们将
Monad作为Functor的独立类(应用程序甚至不存在),因此如果我们想重用需要函子的函数,我们必须经常编写f :: (Monad m, Functor m) => ...。这感觉很愚蠢,因为fmap f x = x >>= return . f将任何 monad 变成了函子。 -
我明白,对于像 monad 这样简单的东西,实际上只有一种方法可以从中定义函子,将 monad 视为比函子。但我的观点是,对于更复杂的结构,可能没有明确的自然层次结构,可能有很多不同的路径,我不知道。所以强制一个层次结构会在如何建模某些问题时产生很多不对称和偏见.