为什么在Haskell中系统地标记严格的记录字段?

ins*_*itu 6 haskell

我已经读过好几次了,好的做法是系统地标记Haskell中严格的记录字段,例如代替

data Foo = Foo { bar :: Bar, quux :: Quux }
Run Code Online (Sandbox Code Playgroud)

data Foo = Foo { bar :: !Bar, quux :: !Quux}
Run Code Online (Sandbox Code Playgroud)

例如,以下是《Haskell Programming from First Principles》(第1134页)的引文:

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

我(认为我)了解严格性是什么,两者之间有什么区别。我不明白的是,为什么后者要系统地进行?

Jon*_*rdy 6

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

  • 您希望严格遍历并保留整个内容,因此懒惰的开销毫无意义;要么

  • 这些字段“很小”(小于或等于指针的大小),并且您希望编译器将它们拆箱以避免不必要的间接访问。(-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)
Run Code Online (Sandbox Code Playgroud)

懒惰的版本在强制执行之前先建立了一个链环。严格的版本可确保Int在每个步骤中对字段进行全面评估。modifyIORef(惰性)和modifyIORef'(严格)也会发生类似的情况。

在非严格类型的字段上添加严格注释几乎是没有意义的,例如,field :: ![Int]仅确保强制第一个 (:)[]构造函数是强制的;它不会使整个列表严格。如果需要,您可能需要一个严格的序列类型,例如Data.Vector。通常不建议将多态字段严格化,如中所述data Foo a = Foo … !a …,因为使用该类型的人可能希望在那里能够依赖懒惰(例如,在使用循环的算法中(“打结”)),并且不得不将类型包装在额外的构造函数中的烦人data Lazy a = Lazy a恢复懒惰。懒惰在方程式推理中也发挥了更好的作用-尽管在实践中,我们经常使用“快速和宽松”的方程式推理,而忽略了严格性和非全部函数。

最终,决定东西是否偷懒或严格的唯一方法是要考虑你需要为你的特定应用程序的语义,并添加(上田刘海,审慎严谨BangPatternsseq严格的功能,如modifyIORef'纹后),如果遇到性能问题或空间泄漏。