数据类型中严格字段的​​优点

时间:2011-12-20 14:16:01

标签: haskell strictness

这可能现在有点模糊,但我一直在想这一段时间。据我所知!,可以确保在构造值之前评估数据构造函数的参数:

data Foo = Bar !Int !Float

我经常认为懒惰是一件好事。现在,当我查看源代码时,我会比! - 更少的变体更频繁地看到严格的字段。

这有什么好处,为什么我不应该把它保持懒惰?

4 个答案:

答案 0 :(得分:36)

除非您在Int和Float字段中存储大量计算,否则可能会产生大量开销,这些开销是通过在thunk中构建的大量繁琐计算构建的。例如,如果您在数据类型中的一个惰性Float字段中重复添加1,它将耗尽越来越多的内存,直到您实际强制该字段计算它。

通常,您希望在字段中存储昂贵的计算。但是如果你知道你不会提前做这样的事情,你可以严格标记字段,并避免在任何地方手动添加seq以获得所需的效率。

作为额外的奖励,当给定标志-funbox-strict-fields GHC将直接将数据类型的严格字段 1 解包到数据类型本身时,这是可能的,因为它知道它们将永远是评估,因此不必分配thunk;在这种情况下,Bar值将包含直接位于内存中Bar值内的Int和Float的机器字,而不是包含指向包含数据的thunk的两个指针。

懒惰是一个非常有用的东西,但是有些时候,它只是妨碍了计算,特别是对于那些总是被看到(并因此被强制)的小字段,或者经常被修改但从未被非常昂贵的计算。严格的字段有助于克服这些问题,而无需修改数据类型的所有用途。

它是否比惰性字段更常见取决于您正在阅读的代码类型;你不太可能看到任何功能树结构广泛使用严格的字段,例如,因为它们从懒惰中受益匪浅。

假设您有一个带有中缀操作构造函数的AST:

data Exp = Infix Op Exp Exp
         | ...

data Op = Add | Subtract | Multiply | Divide

你不想让Exp字段严格,因为应用这样的策略意味着每当你查看顶级节点时就会评估整个AST,这显然不是你想要的从懒惰中受益但是,Op字段永远不会包含您希望延迟到日期的昂贵计算,并且如果您有真正深度嵌套的解析树,则每个中缀运算符的thunk开销可能会变得昂贵。因此,对于中缀构造函数,您希望严格Op字段,但保留两个Exp字段为惰性。

1 只能解压缩单构造函数类型。

答案 1 :(得分:8)

除了其他答案提供的信息外,请记住:

  

据我所知!,可以确保在构造值之前评估数据构造函数的参数

查看deep the parameter is evaluated的方式很有意思 - 它与seq$!评估为WHNF的情况类似。

给定数据类型

data Foo = IntFoo !Int | FooFoo !Foo | BarFoo !Bar
data Bar = IntBar Int

表达式

let x' = IntFoo $ 1 + 2 + 3
in  x'

评估为WHNF产生值IntFoo 6(==完全评估,== NF) 另外这个表达

let x' = FooFoo $ IntFoo $ 1 + 2 + 3
in  x'

评估为WHNF产生值FooFoo (IntFoo 6)(==完全评估,== NF) 但是,这个表达

let x' = BarFoo $ IntBar $ 1 + 2 + 3
in  x'

评估为WHNF产生值BarFoo (IntBar (1 + 2 + 3))(!=完全评估,!= NF)。

要点:如果!Bar的数据构造函数本身不包含严格的参数,Bar参数的严格性将无济于事。

答案 2 :(得分:4)

存在与延迟相关的开销 - 编译器必须为值存储计算创建一个thunk,直到需要结果为止。如果您知道您迟早需要结果,那么强制评估结果是有意义的。

答案 3 :(得分:4)

懒惰是有代价的,否则每种语言都会有它。

费用是2倍:

  1. 设置thunk可能需要更长时间(即最终计算时必须计算的内容的描述),而不是立即执行操作。
  2. 作为对其他thunk等非严格参数的其他thunk的非严格参数的未评估的thunk将使用越来越多的内存。不幸的是,那些tunks还可以保存对不再可访问的内存的引用,即当仅评估thunk时可以释放的内存,从而防止垃圾收集器完成其工作。一个例子是应该更新树中某个值的thunk。假设此值保持在100MB的其他值上。如果没有对旧树的引用,只要不评估thunk,就会浪费这个内存。