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

时间:2010-09-02 03:16:56

标签: list haskell typeclass

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

  

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

为什么没有TypeClass 可列表或提供:功能来统一Data.ByteStringData.ListData.ByteString.Lazy等的内容?这有什么原因,或者这只是传统Haskell的一个元素?使用:作为一个例子是一种轻描淡写,也来自LYAH:

  

否则,bytestring模块有一些类似于Data.List中的函数,包括但不限于head,tail,init,null,length,map,reverse,foldl,foldr,concat, takeWhile,filter等。

7 个答案:

答案 0 :(得分:28)

ListLike包似乎提供了您正在寻找的内容。我永远不明白为什么它不受欢迎。

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

  1. 完全不关心其元素的容器(例如[])
  2. 仅针对特定元素(例如字节串)实现的容器
  3. 对元素具有多态性但需要上下文的容器 (例如Data.Vector.Storable,它会 保持任何类型的可存储 实例)。
  4. 这是一个非常基本的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类型接口也是非平凡的;当你考虑不同的集合语义(例如队列)时,它会变得有点困难。另一个非常大的挑战是可变数据与不可变数据。到目前为止,我所看到的每个尝试(除了一个)都通过创建一个可变接口和一个不可变接口来解决这个问题。我所知道的一个界面确实统一了这两个界面是令人费解的,引用了一堆扩展,并且性能很差。

    附录:bytestrings

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

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

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

答案 1 :(得分:17)

这类课程的主要问题是,即使它存在,它只会提供肤浅的相似性。

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

在严格的字节串中使用cons构建它们可怕,因为每次添加另一个Char时都会复制整个字符串。列表上的 O(1)操作将其转换为Bytestring上的 O(n)操作。

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

事实证明,根据这一点,这类人的效用比实际更为肤浅。

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

我已经在我的密钥包中设计了这个设计空间的一部分,这提供了许多用于索引到容器等的功能,但是我故意避免提供类似列表的API a。)因为它已经被之前完成的成功很少b。)因为上面的渐近关注。

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

答案 2 :(得分:14)

  

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

已经尝试提出一个好的a)序列接口,和b)容器接口,然而,统一不同类型的数据类型,具有不同的类型约束,通常使得结果非标准到足以使它成为很难想象将它们放在基础库中。类似地,对于数组,尽管Vector包现在具有相当通用的接口(基于关联的数据类型)。

有几个项目可以通过单一界面统一这些不同的半相关数据类型,所以我希望我们很快就能看到结果。同样适用于容器类型。结果不会是微不足道的。

答案 3 :(得分:0)

有两个类型为export default { data() { return { vibrateDuration: 5000, }; }, methods: { letsVibrate: function(){ navigator.vibrate(this.vibrateDuration); } } } Foldable的类,它们旨在抽象列表和其他顺序数据结构的一些common1行为。并非所有的数据结构都有这些实例,而且我不知道它们是否对编译器足够透明,以至于它仍然可以对它们进行优化(有人知道这个吗?)

来源:Foldable and Traversable
另见 Why is Haskell missing “obvious” Typeclasses

的答案

答案 4 :(得分:0)

实际上在 OverloadedLists 中有一个 IsList 扩展和一个 GHC.Exts 类。

答案 5 :(得分:-1)

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

答案 6 :(得分:-1)

在Haskell中为类似列表的数据创建类型类没有太多价值。为什么? 由于懒惰。您可以编写一个将数据转换为列表的函数,然后使用该列表。该列表只会被构建为需要的子列表和元素,并且只要前缀没有引用,它们的内存就有资格收集。

提供通用toList函数的类型类有价值 - 但是Data.Foldable已存在。

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