在 Haskell 中,为啥没有 TypeClass 用于可以像列表一样的东西?

Posted

技术标签:

【中文标题】在 Haskell 中,为啥没有 TypeClass 用于可以像列表一样的东西?【英文标题】:In Haskell, why isn't there a TypeClass for things that can act like lists?在 Haskell 中,为什么没有 TypeClass 用于可以像列表一样的东西? 【发布时间】:2011-04-07 02:53:49 【问题描述】:

我正在阅读Learn You a Haskell,我想知道为什么这么多东西都像一个列表一样,而 Prelude 中没有任何东西使用类型类的本机设施来设置它:

" : 的字节串版本被称为 cons 它需要一个字节和一个字节串并将字节放在开头。虽然它很懒,所以即使字节串中的第一个块未满,它也会创建一个新块. 这就是为什么如果要在字节串的开头插入大量字节,最好使用严格版本的 cons, cons'。”

为什么没有 TypeClass listable 或提供: 函数来统一Data.ByteStringData.ListData.ByteString.Lazy 等的东西?这是有原因的,还是这只是传统 Haskell 的一个元素?以: 为例有点轻描淡写,同样来自 LYAH:

否则,bytestring 模块具有类似于 Data.List 中的功能的负载,包括但不限于 head、tail、init、null、length、map、reverse、foldl、foldr、concat、 takeWhile、过滤器等

【问题讨论】:

您能解释一下您想象中的工作原理吗?显然不可能有一个类型类同时包含ByteString[] 作为实例,因为[] 具有类型* -> *ByteString 只是* @Travis Brown:您可以使用一个简单参数化的新类型包装器来做到这一点。这已经被重新发明了几次,但这里有一个例子hackage.haskell.org/packages/archive/iteratee/0.2.1/doc/html/… 如果有一个库可以满足您的需求,那么为什么需要将它包含在适当的语言中? @Robert,如果我有他们,我会给你 10 多票。 @Robert,@luqui,我不明白你的问题,或者你的问题没有意义......让我们假设没有提供= 的类型类,并且Prelude 为每种数字类型提供了不同的函数来测试相等性;此外,每个图书馆都遵循相同的约定。那么问为什么语言本身中没有使用类型类不是一个公平的问题。如果你能回答这个问题,你可能已经准备好回答我的问题了。 【参考方案1】:

ListLike 包似乎提供了您正在寻找的东西。我一直不明白为什么它不受欢迎。

ListLike 不谈,在 Prelude 中没有实现它的一个原因是,如果不调用一些语言扩展(多参数类型类和fundeps 或相关类型)就不可能做到这一点。需要考虑三种容器:

    根本不关心其元素的容器(例如 []) 仅针对特定元素(例如字节串)实现的容器 对元素具有多态性但需要上下文的容器 (例如 Data.Vector.Storable,它将 持有任何类型的可存储 实例)。

这是一个非常基本的 ListLike 风格的类,没有使用任何扩展:

class Listable container where
  head :: container a -> a

instance Listable [] where
  head (x:xs) = x

instance Listable ByteString where --compiler error, wrong kind

instance Listable SV.Vector where
  head v = SV.head    --compiler error, can't deduce context (Storable a)

这里container 有一种*->*。这不适用于字节串,因为它们不允许任意类型;他们有一种*。它也不适用于 Data.Vector.Storable 向量,因为该类不包含上下文(可存储约束)。

您可以通过将类定义更改为来解决此问题

class ListableMPTC container elem | container -> elem where

class ListableAT container where
  type Elem container :: *

现在container 有种*;它是一个完全应用的类型构造函数。也就是说,您的实例看起来像

instance ListableMPTC [a] a where

但你不再是 Haskell98。

这就是为什么即使是一个简单的 Listable 类型的接口也不重要的原因;当您需要考虑不同的集合语义(例如队列)时,它会变得有点困难。另一个真正的大挑战是可变数据与不可变数据。到目前为止,我所见过的每一次尝试(除了一次)都通过创建一个可变接口和一个不可变接口来解决这个问题。我所知道的将两者统一的一个界面令人费解,调用了一堆扩展,并且性能很差。

附录:字节串

完全是我的推测,但我认为我们被字节串作为进化的产物所困。也就是说,它们是低性能 I/O 操作的第一个解决方案,使用Ptr Word8s 与 IO 系统调用进行接口是有意义的。指针上的操作需要可存储,并且很可能那时还没有使多态性工作所需的扩展(如上所述)。现在很难克服他们的势头。具有多态性的类似容器当然是可能的, storablevector 包实现了这一点,但它远没有那么流行。

字节串可以是多态的,对元素没有任何限制吗?我认为最接近 Haskell 的是 Array 类型。这不如低级 IO 的字节串好,因为需要将数据从指针解包为数组的内部格式。此外,数据被装箱,这增加了显着的空间开销。如果您想要未装箱的存储(更少的空间)和与 C 的高效接口,那么指针是可行的方法。一旦你有了 Ptr,你就需要 Storable,然后你需要在类型类中包含元素类型,那么你就需要扩展。

话虽如此,我认为通过可用的适当扩展,这对于任何单个容器实现(模可变/不可变 API)基本上都是一个已解决的问题。现在更难的部分是提出一组合理的类,这些类可用于许多不同类型的结构(列表、数组、队列等),并且足够灵活以供使用。我个人认为这是相对简单的,但我可能错了。

【讨论】:

我是 Haskell 的新手,所以对我来说轻点:为什么 ByteString 有一种 *。这似乎也很随机——为什么不让它多态呢?我想我可以理解目前的推理,但假设 8 位字节不是一个完全不必要的假设吗?为什么不允许ByteString[Word7] 或具有使ByteString 更像String 的类型同义别名的东西...话虽如此,我最喜欢这个答案,因为它试图解释为什么这是'琐碎的。更新 Haskell 语言以标准化 GHC 编译指示是否会使这变得微不足道? @Evan:编辑了我的回复以解决有关字节串的问题。【参考方案2】:

这样一个类的主要问题是,即使它存在,也只能提供表面上的相似性。

使用不同结构构建的同一算法的渐近线会有很大差异。

在严格的字节串的情况下,用 cons 构建它们是可怕的,因为每次添加另一个 Char 时都会复制整个字符串。这个列表上的 O(1) 操作将它变成一个字节串上的 O(n) 操作。

这会导致 O(n^2) 行为,当您实现可能想到的第一个算法时,映射,而使用 cons 构建列表或 Data.Sequence.Seq 是线性时间它可以在 O(n) 中实现,用于字节串或向量以及稍加思考。

事实证明,这样一个类的实用性比实际更肤浅。

我并不是说找不到好的设计,但这样的设计很难使用和优化,而且很可能该设计的可用版本最终不会是 Haskell 98。

我在我的 keys 包中勉强保留了这个设计空间的一部分,它提供了很多用于索引到容器等的函数,但我故意避免提供类似列表的 API a.) 因为它已经以前做过,但收效甚微,b.)由于上述渐近问题。

tl;dr 当底层操作的渐近线发生变化时,您通常希望以非常不同的方式实现算法。

【讨论】:

这是一个非常好的观点,我以前没有想过。但是在考虑之后我不确定我是否完全同意,Haskell base 中已经存在许多函数,它们根据传入的值具有不同的渐近线。Hell even + 根据传入的 Int 或 Integer 具有不同的渐近线,如果你制作了一个自定义矩阵,比如实现 + 的类型,你最终可能会得到一个 O(n) +。还有大量其他语言具有不同渐近线的功能。 (Java ArrayList/LinkedList)。我真的认为实用程序远非肤浅。也许不完美。【参考方案3】:

提供 : 函数来统一 Data.ByteString、Data.List、Data.ByteString.Lazy 等?

已经尝试提出一个好的 a) 序列接口和 b) 容器接口,但是,统一不同种类的数据类型,具有不同的类型约束,通常会使结果不够标准,以至于很难想象将它们放在基础库中。对于数组也是如此,尽管 Vector 包现在有一个相当通用的接口(基于关联的数据类型)。

有几个项目可以将这些不同的半相关数据类型统一到一个接口中,所以我希望我们能很快看到结果。对于容器类型也是如此。结果不会是微不足道的。

【讨论】:

Data.Foldable 是一个合适的解决方案吗? @phil:Data.Foldable 和 Data.Traversable 都很棒,但都没有提供任何接近完整界面的东西。 我对这方面的进展不太抱有希望。我在当前的大多数努力中发现了两个大缺陷。首先是人们希望以不特别适合恕我直言的方式重用现有类型类(Monoid 是一个常见的例子)。第二个是迄今为止我看到的大多数尝试都涉及大型的单体类(例如ListLike),当他们的实例不能完全实现所有必需的方法时,它们反而会阻碍实例编写者。我不认为一个解决方案是不可能的,但它绝对不是微不足道的。【参考方案4】:

有两个类型类称为FoldableTraversable,旨在抽象列表和其他顺序数据结构的一些常见行为。不过,并非所有数据结构都有这些实例,而且我不知道它们是否对编译器足够透明,以至于它仍然可以对它们执行优化(有人知道吗?)

来源:Foldable and Traversable 另请参阅 Why is Haskell missing “obvious” Typeclasses 的答案

【讨论】:

【参考方案5】:

实际上有一个OverloadedLists 扩展和一个GHC.Exts 中的IsList 类与之配套。

【讨论】:

【参考方案6】:

在 Haskell 中为类似列表的数据提供类型类并没有多大价值。为什么? 因为懒惰。您可以编写一个将数据转换为列表的函数,然后使用该列表。该列表只会在需要其子列表和元素时才会被构造,并且一旦没有对前缀的引用,它们的内存就可以被收集。

提供泛型toList 函数的类型类是有价值的——但是,Data.Foldable 中已经存在。

所以基本上,解决方案是实现Data.Foldable 并使用它的toList 函数。

【讨论】:

这只有助于消耗数据。至少,这个问题还涉及通用构造函数。 Foldable 不会给你这样的东西。 我不认为将 Vector 转换为列表然后再返回是一个可行的选择,因为您想要基本上使用 (:) 而不是 cons 作为语法方便。【参考方案7】:

ByteString 不是泛型类型。

在其他语言中,对于所有类似列表的数据结构,都有类似 Sequence 的内容。 我认为这可行,并具有正确的扩展名:

class Seq a b | a -> b where
  head :: a -> b
  isTail :: a -> Bool

# ([a]) is a sequence of a's
instance Seq [a] a where
  head (x:xs) = x
  isTail = (== [])

# ByteString is a sequence of chars
instance Seq ByteString Char

或者试试这个?

type BS a = ByteString
instance List BS

【讨论】:

以上是关于在 Haskell 中,为啥没有 TypeClass 用于可以像列表一样的东西?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Haskell 中没有隐式并行性?

为啥存在量化的 Haskell 中没有“Exist”关键字?

为啥这个 Haskell 程序表现如此糟糕?

为啥 Haskell 没有符号(a la ruby​​)/原子(a la erlang)?

为啥这个 Haskell 代码可以成功地处理无限列表?

为啥 Haskell 异常只能在 IO monad 中捕获?