我看到很多人抱怨标准库中的一些类型类说“Monad 应该需要 Functor”甚至“Monad 应该需要 Applicative”、“Applicative 应该需要 Pointed”、“Num 不应该需要 Show”等,所以,我有一些问题:
是否有关于类型类依赖树具有社区感知的那些“缺陷”的方式的论据,或者这只是历史上如何完成事情的结果?
这方面的巨大变化会破坏现有代码吗?
是否有基本类型类(特别是箭头、monad、applicative 等)的替代实现来实现“正确”的类依赖集?
我看到很多人抱怨标准库中的一些类型类说“Monad 应该需要 Functor”甚至“Monad 应该需要 Applicative”、“Applicative 应该需要 Pointed”、“Num 不应该需要 Show”等,所以,我有一些问题:
是否有关于类型类依赖树具有社区感知的那些“缺陷”的方式的论据,或者这只是历史上如何完成事情的结果?
这方面的巨大变化会破坏现有代码吗?
是否有基本类型类(特别是箭头、monad、applicative 等)的替代实现来实现“正确”的类依赖集?
除了向后兼容性之外,由于 Haskell 处理类型类的方式相当简单,还存在一些问题(主要是装饰性的,但处理起来很乏味):
没有向上的隐含定义:Monad
完全由 just pure
and定义(>>=)
;fmap
并且(<*>)
可以用这些来写。在“适当的”层次结构中,需要写出每个实例。在这种情况下并不算太糟糕,但是随着粒度的增加,每个添加一些小功能的实例数量也会增加。如果类定义可以根据它们自己的函数为超类函数提供默认实现,那么这将大大简化事情,这样(>>=)
在超类中完全包含多个函数的函数可以作为整个相关层次结构的定义。
上下文膨胀:只要有一个Num
需要Show
and的“原因” Eq
,这是因为打印和比较数字相等是很常见的。这些是严格正交的,但是将它们分开意味着执行所有三件事的函数现在必须在其类型中指定所有三个类。这在技术上是一件好事,但同样,随着粒度的增加,函数类型签名也会增加。
单体依赖:可以添加类型类及其超类的层次结构,但不能更改或替换。如果一段代码觉得需要替换它自己的某个通用类型类的版本——比如说,使用类似的东西来替换Monad
——那么层次结构就被切断了;Monad
必须在某种程度上手动提供与使用其他定义的代码的兼容性,并且任何基于其他定义构建的类型类都必须重新实现或翻译,即使仅依赖于两个定义共享的行为子集。
没有明确正确的层次结构:正如上面所暗示的,在类的粒度上需要做出选择。例如,是否Pointed
真的需要存在,或者就Applicative
足够了?这里真的没有普遍理想的答案,也不应该有。
我怀疑首先解决上述问题会更好地替换现有类型类,之后替换类型类的痛苦会少得多,甚至只是一种形式。例如,@luqui 提到的类型类同义词提案将是朝着这个方向迈出的重要一步。
最近在 Haskell-prime 列表上对此进行了讨论,我认为从那里发生的事情是,为了发生层次结构的变化,需要首先实施 Jón Fairbairn 的提议,这解决了非常非常重要的一点 1 @camccan 的列表:类型类可以为其超类定义提供默认实现。我不知道该提案的任何实现,甚至是中途正式规范。
类型类同义词修复点 #2,上下文膨胀。请注意,您已经可以这样做,前提是您可以很好地使用UndecidableInstances
(并且让 GHC 在每次推断类型时扩展同义词......)
至于#4,对#1 和#2 进行适当的修复可以解决大部分实际问题。如果这不仅仅是粒度问题(答案是同义词,包括为整个类型类同义词编写实例),事情会变得更加棘手,但是可以选择多个可能的超类,我认为这在涉及数学时会出现Num
-层次结构。一个解决方案(替代子类?)有一个公平的机会解决#3。
不过,无论如何,我认为这些努力是值得的,因为对 Haskell 进行这些更改(不仅是对层次结构)肯定会使 Haskell 和 Haskell 代码更加灵活,从而在反对者的领导下脱颖而出当前的论点。
我反对替代存在,也不能将与 Applicative-Functor-Monad 层次结构相关的提案归功于我。几年前,我曾抱怨需要一个数学上合理的前奏曲。
类型类层次结构的一个障碍在一开始就存在于幺半群中。例如,整数形成加法和乘法幺半群。 Coq通过多重继承解决了这个问题。
具有建设性数学证明系统的数学层次结构的一大优势是我们可以通过 Curry-Howard 同构从证明中提取程序。对于那些对程序正确性和自动验证感兴趣的人来说,在 Haskell 类型类和 Coq 等证明验证工具之间进行直接转换是很不错的。
我目前使用hmatrix包和朋友。不幸的是,我认为它不能很好地与当前关于 hackage 的 Numeric Prelude 配合使用。理想情况下,要在通用代码中使用优化的 BLAS 和 LAPACK 库,只需实现 Numeric Prelude 中指定的类型类接口。
一些身份不明的人(通过 Vivian McPhail 扮演)提议对前奏中的这一领域进行改革,并通过声明“标准的类层次结构是 Haskell 历史发展的结果,而不是逻辑”来开启他们的提议。这是否属实,我不能说。
上面的提议包括支持向后兼容性的措施,但它似乎无论如何都不完美。
有一个简短但光荣的尝试来创建另一个前奏来收拾一些混乱。