正如@Jubobs 在 cmets 中已经说过的那样 - 不,你不能,但这不一定(总是)效率低下。
在开始解释之前,我想提醒您 Donald Knuth 和他所说的万恶之源是过早优化。因此,请尝试编写一个正确的程序并在以后担心性能问题 - 当您遇到瓶颈时(请确保 haskell 有很好的工具来检测瓶颈!)。
示例
假设您有一个无限列表[1..],并且您想追加5,您仍然可以在haskell 中执行此操作。让我们启动 ghci 并观察一些事情。
> ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/ :? for help
Prelude> let a = [1..] ++[5] :: [Int]
首先我们注意到 - let-bindings 工作很懒惰并且没有打印任何东西(还)。我们甚至可以进一步检查 a 的评估程度。
Prelude> :sprint a
a = _
虽然像filter (== 5) a 这样的表达式永远不会终止,但我们仍然可以用我们的列表a 做一些有用的事情。
Prelude> let b = map (+2) a
Prelude> take 10 b
[3,4,5,6,7,8,9,10,11,12]
并再次检查a。
Prelude> :sprint a
a = 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : _
我们只评估了前十个元素。
结束这个例子,我们看到——即使你“追加”5,它也可能永远不会被使用——因此“追加”永远不会发生,这比我们有一个廉价的“追加”更有效发生了。
(请注意,这种惰性会引入一些内存开销,可能会导致麻烦,请参阅Lazy Evaluation - Space Leak 了解更多信息。)
替代方案
正如其他人已经说过的那样 - 除了使用好的旧列表之外,还有其他选择。
- 如果您经常追加但将列表用作“累加器” - 最好在返回之前预先追加和反转列表。这是
O(1) 对于所有 (:) 操作和 O(n) 对于反向 - 与 O(n) 对于每个 (++) 步骤相比。
-
使用不同的数据结构:
-
差异列表: 您可以在Learn you a haskell for great good 上阅读,并从那里引用:
与[1,2,3] 之类的列表等效的差异列表将是函数\xs -> [1,2,3] ++ xs。一个普通的空列表是[],而一个空的差异列表是函数\xs -> [] ++ xs。
-
Sequence:来自Data.Sequence,它支持高效的prepend/append-操作和在边缘查找元素。
$ stack ghci --package containers
Prelude> import Data.Sequence
Prelude Data.Sequence> let a = fromList [1..4] :: Seq Int
Prelude Data.Sequence> (a |> 5)
fromList [1,2,3,4,5]
Prelude Data.Sequence> 0 <| (a |> 5)
fromList [0,1,2,3,4,5]
-
Set - 它不像列表那样排序,但仍然支持高效的 (O(log n)) 插入,但只允许唯一的插入。
$ stack ghci --package containers
Prelude> import Data.Set
Prelude Data.Set> let a = fromList [1..4] :: Set Int
Prelude Data.Set> insert 5 a
fromList [1,2,3,4,5]
Prelude Data.Set> insert 4 a
fromList [1,2,3,4]