为什么 Haskell 标准库没有更多地使用多态性?

29

我正在学习Haskell,类型类似乎是一种强大的方式,可以创建类型安全的多态函数。但是很多Haskell预定义函数并未使用它们,具体来说:

  • 大多数列表函数无法与其他数据结构一起使用(例如,foldrlength 仅适用于列表,无法用于数组)。

  • Data.ByteString 这样的模块是不可用的,除非你使用 import qualified,因为它们包括与Prelude函数名称相同的函数。

如果标准库使用类型类,则这两个问题似乎都将消失(请让我知道是否完全正确)。 我有两个问题:

  1. Prelude 是出于技术或设计原因才这样吗?还是只是出于历史原因?

  2. 看起来有一些库(例如Data.Foldable,如果我没有误解,Scrap Your Boilerplate),它们用通用的替代品替换了标准Prelude函数。 有计划将这些想法纳入Haskell的未来版本中吗?


1
没有想法,但有很多事情可以在这之前做,比如让Monad类依赖于Appliciative。使用“pure”或“unit”代替“return”。摆脱“map”,只使用“fmap”也是一个选择,但我不认为这会发生。如果这样做,太多的事情会被打破。 - Robert Massaioli
1
我认为现在是时候增加更多的多态性,对类型类进行一些修正(例如Robert提到的Monad / Applicative fix),并使一些编译器扩展成为标准。通过演化只能走得这么远。对于语言来说,重要的是要做出明确的决策,并解决过去的大部分差距和错误。我认为转变不必是一种震荡疗法,而可以合理地平稳过渡(例如Python 2到3)。 - Landei
4个回答

19

“标准” Haskell(Prelude + base + 可能还有其他)不使用更多的多态有一个非常好的实用原因:

设计通用类型类是困难的。像列表、数组和“bytestrings”(个人不认为 Bytestring 是容器)这样的容器类型抽象的良好设计不是一直在等着被包含在 Haskell 2012 中的。有一些设计,例如 Listlike 和 Edison 类,许多人一直在解决这个问题,但除了 Foldable 和 Traversable 之外,没有人提出任何令人信服的设计。


他们可以直接使用其他语言中已经可用的库,例如C++ STL。 - Puppy
11
STL(标准模板库)在设计上非常注重可变结构和指针。即使是如此,它也暴露了大量的实现细节。 - sclv

11

Haskell基础库曾经更加多态化 - 列表推导式适用于任何Monad,map++不仅限于List,也许还有其他东西。

但是当时的人们认为这会给初学者带来混乱的错误信息,而不是初学者的人可以使用特定的多态版本。


+1,非常好的观点。特别是在涉及多态性和“无限类型”时,没有人会抱怨GHC的错误消息难以理解。 - John L
2
这里是Simon Peyton Jones在解释为什么Monad推导被移除出Haskell的引用:http://hackage.haskell.org/trac/ghc/ticket/4370#comment:1。但他并没有做出强烈的声明。抱歉,目前我手头没有其他资料。 - Antoine Latter

8
  1. 虽然在基础库中,特别是Prelude中有很多历史遗留问题,但我认为任何概括都会遭到技术方面的反对。主要问题是速度 - 如果你的函数有类型类约束,那么你将传递一个字典来执行类型类函数,并可能需要更多空间进行专门化。

  2. 一些库,比如SYB,使用了Haskell中不存在的扩展功能。首先要做的是正式化并建立对这些特性的支持。查看Haskell'文档以了解Haskell的发展方向以及如何影响它的进程。


3
嗯, GHC有一个很好的特化器,速度不是真正的问题。List是“迭代器”数据结构,在命令式语言中与foreach的含义相同--如果可以转换为列表,然后执行列表操作以获得所需内容,则没有理由使其成为类型类多态的。另一方面,有些操作应该更加多态化,例如zip(参见ZipList)。 - luqui
@luqui:所以,对于折叠数组(例如),惯用的方法是将其转换为列表,对列表进行折叠,然后将其转换回数组?我猜这很有道理,尽管能够对树等东西进行折叠仍然很好。而其他列表函数,如“length”,与迭代无关。 - shosti
1
@eman,除非您不将其转换回数组,因为fold不返回列表,而是返回汇总值。请参见标准类型类“Foldable”和“Traversable”,以了解可以折叠和迭代的内容。 - luqui
为什么在运行时需要传递一个字典给带有类型类约束的函数?它们不应该在编译时被省略吗? - Bill
1
@Bill 并非总是如此。为了消除字典,编译器通常需要进行函数专门化(有很多论文讨论这个问题),这可能会增加代码量。正如 Mark Jones 很久以前所展示的那样,这种情况并不经常发生。另一个问题是它容易导致分离编译出现麻烦(所以要整体编译,该死的!)。 - Thomas M. DuBuisson

6
《Real World Haskell》在Monad Transformers章节中对此有一些见解:
在理想情况下,我们会从过去中脱离出来,将Prelude切换到使用Traversable和Foldable类型吗?可能不会。学习Haskell已经足够令初学者兴奋了。当我们已经理解了functor和monad时,抽象的Foldable和Traversable很容易掌握,但它们会让初学者摄入过多的抽象概念。对于教授语言,好的做法是map操作列表而不是functor。

11
我持不同意见。例如,在Java中,扩展的for循环适用于数组、列表、集合等等,我从未听到过新手对此有任何抱怨。反应通常恰恰相反:“太酷了,我可以修改自己的类,让它们也能工作!”因此,将映射(Map)变成多态并不会使其更难使用,反而可能会促进试验,使自制数据结构成为“可映射”的。 - Landei
6
是的,我必须说,在 Haskell 中所有棘手的事情中,泛型函数并不排在前列(所有数字函数都是泛型的,没有人会遇到任何麻烦)。import qualified 和晦涩的前缀要更加令人望而却步,特别是对那些有面向对象编程背景的人来说。 - shosti
数值类有特殊的语言支持,请参阅语言定义中的关键字 default。这是因为我们对于何时将文字量词消歧为具体的数字类型有相当好的理解。 - jmg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接