代码之家  ›  专栏  ›  技术社区  ›  wen

哈斯克尔“集合”语言设计

  •  20
  • wen  · 技术社区  · 15 年前

    为什么Haskell实现如此关注链表?

    例如,我知道数据。序列更有效 对于大多数列表操作(除了 cons 操作),并被大量使用; 然而,在句法上,它“几乎不受支持”。Haskell在函数抽象方面做了很多工作,比如Functor和Foldable类,但是它们的语法与默认列表的语法不兼容。

    如果在一个项目中,我想用序列来优化和替换我的列表,或者如果我突然想要支持无限的集合,并用列表来替换我的序列,那么所导致的代码更改是令人憎恶的。

    所以我想我的疑惑可以具体化为以下问题:

    1. 为什么 map 等于 (Functor f) => (a -> b) -> f a -> f b ?
    2. 为什么不能 [] 和 (:) 函数用于例如Data.Sequence中的type?

    我真的希望有一些解释,这不包括“向后兼容”或“它只是增长的方式”,但如果你认为没有,请让我知道。我们也欢迎任何相关的语言扩展。

    5 回复  |  直到 15 年前
        1
  •  21
  •   Heatsink    12 年前

    在解释原因之前,这里有一个问题的总结,以及你可以做些什么。施工人员 [] 和 (:) 为列表保留,无法重新定义。如果计划对多个数据类型使用同一代码,则定义或选择表示要支持的接口的类型类,并使用该类中的方法。 这里有一些对列表和序列都有效的广义函数。我不知道 (:) ,但你可以自己写。

    • fmap 而不是 map
    • mempty 而不是 []
    • mappend 而不是 (++)

    如果计划一次性替换数据类型,则可以定义自己的名称,并在以后重新定义它们。

    -- For now, use lists
    type List a = [a]
    nil = []
    cons x xs = x : xs
    
    {- Switch to Seq in the future
    -- type List a = Seq a
    -- nil = empty
    -- cons x xs = x <| xs
    -}
    

    请注意 [] 和 (:) 是构造函数:您还可以使用它们进行模式匹配。模式匹配特定于一个类型构造函数,因此如果不重写模式匹配项代码,就无法扩展模式以处理新的数据类型。


    为什么哈斯凯尔有这么多列出来的东西

    列表通常用于表示顺序 计算 ,而不是数据。在命令式语言中,可以使用循环来构建一个集合,该循环创建元素并将它们逐个插入集合中。在Haskell中,通过创建一个列表,然后将该列表传递给 Set.fromList . 由于列表与这种计算抽象非常匹配,因此它们有一个不太可能被另一个数据结构取代的位置。

    事实上,有些函数可能是泛型的,但它们是特定于列表的。一些常见功能如 地图 被列为特定的列表,这样新用户就可以少学了。特别是,它们提供了更简单和(已决定)更容易理解的错误消息。由于可以改用泛型函数,所以问题实际上只是语法上的不便。值得注意的是,Haskell语言 实现 很少有列表专用代码,因此新的数据结构和方法可以与“内置”的数据结构和方法一样高效。

    有几个类是列表的有用概括:

    • 函子 供应品 fmap ,概括了 地图 .
    • 幺半群 提供对具有类似列表结构的集合有用的方法。空名单 [] 一般化为其他容器 记忆 ,和列表连接 (++) 一般化为其他容器 马彭德 .
    • 实用的 和 单子 提供用于将集合解释为计算的方法。
    • 可穿越 和 可折叠 提供在集合上运行计算的有用方法。

    其中,只有函子和单子在有影响力的Haskell 98规范中,所以其他的在不同程度上被图书馆作者忽略了,这取决于图书馆是什么时候编写的,以及维护的积极性。核心库在支持新接口方面做得很好。

        2
  •  7
  •   li.davidm    15 年前

    我记得在某个地方读到过 map 默认情况下是用于列表的,因为Haskell的新成员如果犯了错误并看到关于“函子”的复杂错误(他们不知道这个错误)就会被推迟。因此,他们都有 地图 和 fmap 而不是仅仅 地图 .

    编辑:“某处”是《蒙纳读者》第13期,第20页,脚注3:

    你可能会问为什么我们需要一个单独的映射函数。为什么不把水流带走 只列出map函数,并将fmap重命名为map?好吧,这是个好问题。这个 通常的观点是,如果有人只是学习Haskell,而不正确地使用map,那么 与其说是关于函子,不如说是关于列表的错误。

    为了 (:) ,和 (<|) 功能似乎是一个替代品。我不知道 [] .

        3
  •  5
  •   stephen tetley    15 年前

    一个挑剔的数据序列对于“列表操作”来说效率不高,对于序列操作来说效率更高。也就是说,Data.List中的很多函数都是顺序操作。Sequence必须为cons(<|)做相当于list(:)的更多工作,它的内存表示也比list大一些,因为它由finger tree和Deep两种数据类型组成。

    列表的额外语法是好的,它在列表擅长的地方找到了最佳点-cons(:)和左边的模式匹配。序列是否应该有额外的语法还有待进一步的讨论,但是由于使用列表有很长的路要走,而且列表本身很简单,所以必须有好的语法。

    List不是字符串的理想表示形式-内存布局效率低下,因为每个字符都用构造函数包装。这就是为什么引入了ByteStrings。尽管它们是作为数组布局的,但ByteStrings必须做一些管理工作-[Char]如果使用短字符串,仍然具有竞争力。在GHC中,有一些语言扩展来赋予ByteStrings更多类似字符串的语法。

    另一个主要的lazy函数Clean总是将字符串表示为字节数组,但它的类型系统使这一点更加实用——我相信ByteString库在幕后使用了unsafeperformio。

        4
  •  4
  •   robx    12 年前

    对于版本7.8,ghc支持重载列表文本,比较 manual . 例如,假设适当 IsList 实例,您可以编写

    ['0' .. '9']             :: Set Char
    [1 .. 10]                :: Vector Int
    [("default",0), (k1,v1)] :: Map String Int
    ['a' .. 'z']             :: Text
    

    (引自文件)。

        5
  •  2
  •   Ozgur    15 年前

    我很肯定这不会回答你的问题,但仍然是。

    我希望Haskell有更自由的函数名(mixfix!) a la Agda . 然后,列表构造函数的语法( : , [] )不会的 魔术 ;允许我们至少隐藏列表类型,并对自己的类型使用相同的标记。

    在列表类型和自定义序列类型之间迁移时,代码更改的数量将是最小的。

    关于 map ,你真幸运。您始终可以隐藏地图,并将其设置为fmap自己。

    import Prelude hiding(map)
    
    map :: (Functor f) => (a -> b) -> f a -> f b
    map = fmap
    

    前奏曲是伟大的,但它不是哈斯克尔最好的部分。

    推荐文章