为什么要在Haskell中系统地标记记录字段?

时间:2018-05-24 06:25:26

标签: haskell

我已多次阅读过,在Haskell中系统地标记记录字段 strict 是一种好习惯,例如而不是

data Foo = Foo { bar :: Bar, quux :: Quux }

DO

data Foo = Foo { bar :: !Bar, quux :: !Quux}

例如,这里引用了Haskell Programming from First Principles,第113页:

  

遵循的一条好规则是在脊椎中懒惰,严格的叶子!

我(我想)明白什么是严格,两者之间有什么区别。我不明白为什么后者系统地理解?

1 个答案:

答案 0 :(得分:4)

一个常见的经验法则是在以下情况下使数据结构严格:

  • 你希望严格地遍历整个事物并保留它,所以懒惰的开销没有任何意义;或

  • 字段“小” - 不等于或等于指针的大小 - 您希望编译器将它们拆开以避免不必要的间接。 (-funbox-small-strict-fields默认启用,因为GHC 7.7。)

这在实践中往往与“脊椎中的懒惰,严格的叶子”大致相同,因为数据结构通常会因懒惰而受益,但内容通常不会。

“系统地”执行此操作是帮助避免空间泄漏的一种天真的方式 - 例如,如果您在不强制同时修改数据的情况下重复修改数据结构(例如累加器):

ghci> data Lazy = Lazy { lazyField :: Int } deriving (Show)

ghci> data Strict = Strict { strictField :: !Int } deriving (Show)

ghci> modifyLazy (Lazy field) = Lazy { lazyField = field + 1 }

ghci> modifyStrict (Strict field) = Strict { strictField = field + 1 }

ghci> lazy = iterate modifyLazy (Lazy 0) !! 1000000

ghci> strict = iterate modifyStrict (Strict 0) !! 1000000

ghci> :set +s

ghci> lazy
Lazy {lazyField = 1000000}
(0.76 secs, 251,792,080 bytes)

ghci> strict
Strict {strictField = 1000000}
(0.52 secs, 178,173,544 bytes)

懒惰的版本在强行之前建立了一系列的thunk;严格版本会在每一步都对Int字段进行全面评估。类似的事情发生在modifyIORef(懒惰)和modifyIORef'(严格)。

对具有非严格类型的字段添加严格注释几乎没有意义,例如,field :: ![Int]仅确保第一个 (:)或{{ 1}}构造函数是强制的;它并没有使整个列表严格。如果需要,您可能需要一个严格的序列类型,如[]。并且通常不建议对多态字段进行严格限制,如在Data.Vector中,因为使用该类型的人可能希望能够依赖于那里的懒惰 - 例如,在使用循环的算法中(“绑结”) “) - 并且令人讨厌的是必须在类似data Foo a = Foo … !a …的额外构造函数中包装类型以重新获得懒惰。懒惰也可以通过等式推理更好地发挥作用 - 尽管在实践中我们经常使用“快速和宽松”的等式推理来忽略严格性和非全部功能。

最终,决定某些内容是否应该是懒惰或严格的唯一方法是考虑您的特定应用程序所需的语义,并添加明智的严格性(字段上的刘海,data Lazy a = Lazy aBangPatterns,如果遇到性能问题或空间泄漏,在进行性能分析后需要严格的seq}函数。