在Haskell中,为什么没有TypeClass可以像列表一样工作呢?
我正在阅读学习你一个Haskell ,我想知道为什么这么多东西都像列表一样,Prelude中没有任何东西使用types的本地工具来设置它:
“字节串的版本:被称为cons它需要一个字节和一个字节串,并且把字节放在开始位置,这很懒,所以即使字节串中的第一个块没有满,它也会产生一个新的块,这就是为什么如果你要在字节串的开头插入大量的字节,最好使用严格版本的缺点。
为什么没有TypeClass 列表或者提供了:
函数来统一Data.ByteString
, Data.List
, Data.ByteString.Lazy
等? 这是否有一个原因,或者这只是遗留的Haskell元素? 使用:
作为一个例子有点轻描淡写,也来自LYAH:
否则,字节串模块具有类似于Data.List中的函数的负载,包括但不限于头部,尾部,init,空值,长度,映射,反转,foldl,foldr,concat,takeWhile,filter等等
ListLike包似乎提供你在找什么。 我从来没有明白为什么它不是更受欢迎。
ListLike除此之外,Prelude没有实现的一个原因是因为如果不调用某些语言扩展(多参数types的类和fundeps或相关的types),就不可能做得很好。 有三种容器需要考虑:
- 根本不关心它们的容器(例如[])
- 仅针对特定元素(如字节串)实现的容器
- 在元素上多态但需要上下文的容器(例如Data.Vector.Storable,它将保存任何types的可存储实例)。
这是一个非常基本的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
有kind *->*
。 这对于字节串不起作用,因为它们不允许任意types; 他们有*
。 它也不适用于Data.Vector.Storable向量,因为该类不包含上下文(Storable约束)。
你可以通过改变你的类定义来解决这个问题
class ListableMPTC container elem | container -> elem where
要么
class ListableAT container where type Elem container :: *
现在container
有种*
; 它是一个完全应用的types构造函数。 也就是说,你的实例看起来像
instance ListableMPTC [a] a where
但你不再是Haskell98。
这就是为什么即使是一个简单的Listabletypes的接口是不平凡的; 当你有不同的集合语义来解释(例如队列)时,它会变得更难一点。 另一个非常大的挑战是可变数据与不可变数据。 到目前为止,我所见过的每一个尝试(除了一个)都是通过创build一个可变接口和一个不可变接口来解决这个问题。 我所知道的将这两者统一起来的界面是弯曲的,引发了一系列的扩展,并且性能相当差。
附录:字节串
我完全猜测,但我认为我们坚持把字节串作为进化的产物。 也就是说,它们是低性能I / O操作的第一个解决scheme,使用Ptr Word8
与IO系统调用进行接口是有意义的。 指针上的操作需要可存储,并且最有可能是必要的扩展(如上所述)使多态性工作不可用。 现在很难克服它们的势头。 具有多态性的类似容器当然是可能的,可存储的向量包实现了这一点,但是它并没有受到任何的欢迎。
字节串是否可以是多态的,对元素没有任何限制? 我认为最接近Haskell这是arraystypes。 这不如低级IO的字节串,因为数据需要从指针解压缩到数组的内部格式。 数据也是盒装的,这增加了显着的空间开销。 如果你想要无箱的存储空间(更小的空间)和高效的C接口,指针是要走的路。 一旦你有了一个Ptr,你需要Storable,然后你需要在types类中包含元素types,所以你只需要扩展。
这就是说,我认为,有了适当的扩展可用,这对于任何单个容器实现(modulo mutable / immutable API)来说基本上是一个解决的问题。 现在比较困难的部分是提出一组可用于许多不同types的结构(列表,数组,队列等)的合理类,并且足够灵活以便有用。 我个人会认为这是比较简单的,但我可能是错的。
提供了:函数来统一Data.ByteString,Data.List,Data.ByteString.Lazy等?
有人试图想出一个好的a)序列接口,b)容器接口,但是统一不同types的数据types,不同types的约束,一般会使得结果不够规范,很难想象把它们放在基础库中。 同样,对于数组,虽然Vector包中有一个相当通用的接口(基于关联的数据types)。
有几个项目用统一的接口统一这些不同的半相关数据types,所以我希望我们很快会看到一个结果。 类似的容器types。 结果不会是微不足道的。
这样一个阶级的主要问题是,即使存在,也只会提供一个肤浅的相似之处。
使用不同结构构build的相同algorithm的渐近性会有很大的不同。
在严格字节串的情况下,用cons来构build它们是非常糟糕的 ,因为每当你添加另一个string时,就会复制整个string。 这个在列表上的O(1)操作将其变成对Bytestring的O(n)操作。
当你实现可能想到的第一个algorithm时,这会导致O(n ^ 2)的行为,而构build一个列表或Data.Sequence.Seq与cons是线性时间,它可以实现在O(n)对于字节串或向量以及一点想法。
事实certificate,这样一个class级的实用性是比实际更肤浅的。
我并不是说不能find好的devise,但是这样的devise将很难使用,并且对devise的可用版本进行优化和devise可能不会被认为是Haskell 98。
我已经在我的keys包中提供了这个devise空间的一部分,它提供了很多用于索引到容器等的function,但是我故意避免提供一个类似列表的API a),因为它之前已经完成一点成功和b。)因为上面的渐近关注。
tl; dr通常,当基本操作的渐近性发生变化时,要非常不同地实现algorithm。
有两个types叫做Foldable
和Traversable
,它们旨在抽象列表和其他顺序数据结构的一些常见行为。 并不是所有的数据结构都有这些实例,我不知道它们是否对编译器足够透明,以至于它仍然可以对它们进行优化(是否有人知道这个问题?)
来源: 可折叠和可穿越
另请参阅此答案为什么Haskell缺less“显而易见”的types类
ByteString不是一个genericstypes。
在其他语言中,对于所有类似列表的数据结构,都有类似于Sequence
的内容。 我认为这是有效的,正确的扩展名:
class Seq ab | 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中为类列表数据提供types类没有太大的价值。 为什么? 因为懒惰。 你可以写一个函数把你的数据转换成列表,然后使用这个列表。 该列表将只被构build为其子列表和元素被请求,并且只要没有引用保留在前缀上,其内存就有资格收集。
对于提供一个通用的toList
函数的types类是toList
– 但是,它已经存在于Data.Foldable
。
所以基本上,解决scheme是实现Data.Foldable
并使用它的toList
函数。