List Haskell:从后面访问列表

List Haskell:从后面访问列表,list,haskell,append,List,Haskell,Append,今天我开始学习哈斯克尔。我是函数式语言的新手,我非常喜欢Haskell 然而,我对它的设计有一个问题困扰着我:据我目前所知,访问列表后面的元素要比访问前面的元素复杂得多(类似于xs:xwherexs:[a]和x::a似乎不可能) (据我所知)可以将一个列表附加到另一个列表(xs++[a]),但在运行时成本会更高(需要遍历整个列表),并且不能用于模式匹配 为什么Haskell缺少这样的操作?这些不是语言的问题,只是列表数据类型,它有一些特殊的语法,但在其他方面并不完全是“Haskell不可分割的一

今天我开始学习哈斯克尔。我是函数式语言的新手,我非常喜欢Haskell

然而,我对它的设计有一个问题困扰着我:据我目前所知,访问列表后面的元素要比访问前面的元素复杂得多(类似于
xs:x
where
xs:[a]
x::a
似乎不可能)

(据我所知)可以将一个列表附加到另一个列表(
xs++[a]
),但在运行时成本会更高(需要遍历整个列表),并且不能用于模式匹配


为什么Haskell缺少这样的操作?

这些不是语言的问题,只是列表数据类型,它有一些特殊的语法,但在其他方面并不完全是“Haskell不可分割的一部分”。在C语言中,单链表也有同样的问题,但这显然不是C编程语言的问题


如果您想要一个带有尾部指针的双链接列表,那么创建这样的数据类型并使用它!您可能需要了解更多的数据类型(请参阅软件包和软件包中的示例)。

这是纯列表数据结构的问题,而不是haskell本身的问题。您可以阅读本文以了解更多有关其他纯数据结构的信息,这些纯数据结构可以在此类操作中具有更好的性能

Haskell中的列表数据类型是链表,因此查找使用O(n)时间。如果您需要频繁访问列表的后面,您可能需要查看 开头和结尾都有O(1)加法

为了回答为什么Haskell使用此数据结构作为“标准容器”(如C和数组),这是因为Haskell是一种纯函数式语言,因此更喜欢纯函数式数据结构(不可变和持久),以便进一步阅读。要在Haskell中使用非功能性数据结构,需要它位于列表数据类型的
IO
或monad.

data [a] = [] | a : [a]
定义如下。只有两种模式可以匹配:
[]
x:xs
,其中
x
是头,
xs
是尾

在列表前加前缀

a = 1 : 2 : []
b = 0 : a

(:)无论何时需要,都不要惊慌失措地翻开你的清单。在将列表交给递归函数之前反转列表,或者反转fold()的最终结果,这并不少见。

列表是一种内置类型,由Haskell语言本身描述,不是吗?如果Haskell中的列表按原样定义,那是因为语言设计,就像C内置类型是由语言定义的一样。C语言中的列表不是语言的一部分,就像非本地Haskell数据类型不是Haskell语言的一部分一样。@peoro列表的内置程度纯粹是语法上的。想象一下,如果列表被定义为
数据列表a=Nul | Cons a(列表a)
。它的内置程度与您的问题是正交的-如果您想要O(1)访问尾部,那么请使用跟踪尾部的数据结构。今天我学习了如何声明新类型,并理解了您告诉我的内容
:-)
,这取决于您的要求。列表是一种内置类型,由Haskell语言本身描述,不是吗?如果是这样的话,haskell的列表取决于语言设计。@如果你一直指出这一点,但人们告诉你的基本上是“你使用了错误的工具”,haskell的工具可以满足你的需要,但你似乎并不感兴趣。类似的抱怨可能是“C数组不能做O(1)cons”和“Python‘列表’不是O(log(n))查找”。显然,这些抱怨是可疑的,因为这只是开发人员滥用了特定的数据结构。@TomMD,我没有“一直指出这个”,我只对这一点评论了一次(对你和这个答案)。我同意,如果在C语言中我想要一个列表,数组对我的目的是不好的。对于我的问题(以及这些评论),我并不是说Haskell列表不好(就像C数组不坏一样),我也不是在抱怨它!我只是想知道为什么它们是这样设计的;它们是Haskell语言的一部分:因此列表设计取决于Haskell的设计。总而言之,我不需要你和尤拉斯建议的不同列表,我只是想知道为什么内置列表是这样工作的。你的第二句话就是我想要的。再加上@ephemient-answer,这让我明白了为什么haskell列表是这样设计的。谢谢
:-)
我不确定数据是否正确。序列的复杂性与双链接列表类似,就像数据一样。添加元素时,序列必须做一些平衡工作。JoinList有时被称为Monoidal list,它不会进行任何平衡,因此在构建时应该更有效。不过,不要在Hackage上使用JoinList,它写得很糟糕,需要更新(我是作者)-实际上,我希望Data.Sequence即使在JoinList应该获胜的事情上也能表现得更好。@Stephen,我同意,所以我将它改为更具体地说明我们感兴趣的运行时间。我不知道我在想什么,当我说它类似于双链表,因为它显然不是这样的!正确的!我不认为名单必须是不变的,因此,他们只能在前线或后部延伸,但不能延伸到双方。谢谢 (:) <-- b / \ 0 (:) <-- a / \ 1 (:) / \ 2 []
a = 1 : 2 : []
b = a ++ [3]
(:) <-- a (:) <-- b / \ / \ 1 (:) 1 (:) / \ / \ 2 [] 2 (:) / \ 3 []
a = 0 : a
b = 0 : [ x+1 | x <- b ]
(:) <-- a (:) <-- b / \ / \ 0 (:) <-- a 0 (:) <-- [ x+1 | x <- b ] / \ / \ 0 (:) <-- a 1 (:) <-- [ x+1 | x <- [ x+1 | x <- b ] ] ... ...