以下示例显示了Data.Sequence
的问题:
{-# LANGUAGE BangPatterns #-}
module Main where
import qualified Data.Sequence as S
import Data.Sequence ((|>), ViewL(..))
import Data.List (foldl')
import GHC.AssertNF
update !init !x = init |> x
main =
do let !seq = foldl' update S.empty [1..10]
assertNF seq
打印
Parameter not in normal form: 1 thunks found:
Deep (One (S# 1)) (_thunk (Deep (One ...) (_thunk ... ... ... ...) (Three ... ... ...) 93) (S# 95) (S# 96) (S# 97)) (Three (S# 98) (S# 99) (S# 100)) 100
但是,Data.Sequence
的文档声称所有操作都是严格的,那么为什么插入后树没有完全评估?是否需要保证一些非常复杂的界限?
我们非常不喜欢这里的懒惰,所以我想知道是否有更严格的|>
或类似的数据结构支持从后面追加到后面和枚举,可能是更有效的?
答案 0 :(得分:13)
Data.Sequence是一个元素严格,数字严格的数据结构,带有一个懒惰的脊柱。
data FingerTree a
= Empty
| Single a
| Deep {-# UNPACK #-} !Int !(Digit a) (FingerTree (Node a)) !(Digit a)
这意味着将对已插入的值进行求值评估,但仅在查询时评估结构的主干。 这通常是你想要的 - 较小的数据结构。
如果你想对它施加一个严格的脊椎,你将导致更高的插入成本,但你可能会获得其他地方。
尝试将fingertrees包修改为spine严格并查看它是否实际更快 - 我将有兴趣知道结果。
顺便说一句:“我们非常不喜欢这里的懒惰”,这不是避免脊椎懒惰数据结构的好理由。如果[a]是严格的脊椎,那将是一种可怕的数据类型。 Data.Sequence也是如此。您应该量化为什么spine-strictness对于您的用例是错误的语义。
答案 1 :(得分:11)
手指树的良好表现取决于懒惰。引用Hinze, Ralf; Paterson, Ross (2006), "Finger Trees: A Simple General-purpose Data Structure":
... 虽然结构使得懒惰的必要用途,但它也是合适的 对于提供惰性评估原语的严格语言。
并在分析其属性时:
......因此,在一系列操作中,平均成本是不变的。
如果使用挂起子树,则在持久设置中保持相同的边界 懒惰的评价。这可以确保脊柱深处的变换不会 发生,直到后续的操作需要下降到那么远。因为 以上属性的安全和危险的数字,到那个时候就足够了 廉价的浅层操作将被执行以支付这个昂贵的 评价。
因此,如果将实现从lazy更改为strict,您可能会失去良好的时间复杂性界限(取决于您使用的操作和顺序)。