在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
的逻辑理由也是 Eq
s 太弱了。例子 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
实例的问题是分开的。原则上,我们可以定义一个具有不兼容 Functor
和 Monad
实例的违法数据类型。在我们的程序中,我们仍然只能使用那个 Functor MyType
实例和那个 Monad MyType
实例,无论 Functor
是否是 [=22= 的超级 class ].
为了澄清我的问题,让我用大致相同的方式重新表述一下:
为什么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
的逻辑理由也是 Eq
s 太弱了。例子 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
实例的问题是分开的。原则上,我们可以定义一个具有不兼容 Functor
和 Monad
实例的违法数据类型。在我们的程序中,我们仍然只能使用那个 Functor MyType
实例和那个 Monad MyType
实例,无论 Functor
是否是 [=22= 的超级 class ].