我正在启动Haskell并且正在查看一些使用“!”定义数据类型的库。 bytestring库中的示例:
data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
{-# UNPACK #-} !Int -- offset
{-# UNPACK #-} !Int -- length
现在我看到this question作为对这意味着什么的解释,我想这很容易理解。但我现在的问题是:使用它有什么意义?由于表达式将在需要时进行评估,为什么要强制进行早期评估?
在这个问题的第二个答案中C.V.汉森说:“[...]有时懒惰的开销可能过多或浪费”。这是否意味着它用于节省内存(保存值比保存表达式便宜)?
解释和示例会很棒!
谢谢!
[编辑]我想我应该选择一个没有{ - #UNPACK# - }的例子。所以让我自己做一个。这会有意义吗?是的,为什么以及在什么情况下?
data MyType = Const1 !Int
| Const2 !Double
| Const3 !SomeOtherDataTypeMaybeMoreComplex
答案 0 :(得分:13)
这里的目标不是严格,而是将这些元素打包到数据结构中。如果没有严格性,那么这三个构造函数参数中的任何一个都可以指向堆分配的值结构或堆分配的延迟评估thunk。严格来说,它只能指向堆分配的值结构。使用严格和压缩结构,可以将这些值内联。
由于这三个值中的每一个都是指针大小的实体,并且无论如何都是严格访问的,因此强制使用严格的打包结构可以在使用此结构时保存指针间接。
在更一般的情况下,严格性注释可以帮助减少空间泄漏。考虑这样的情况:
data Foo = Foo Int
makeFoo :: ReallyBigDataStructure -> Foo
makeFoo x = Foo (computeSomething x)
如果没有严格注释,如果你只是调用makeFoo
,它将构建一个Foo
指向指向ReallyBigDataStructure
的thunk,将其保留在内存中直到某些东西强迫thunk评估。如果我们改为
data Foo = Foo !Int
这会强制computeSomething
评估立即进行(好吧,只要某些东西强制makeFoo本身),这就避免了对ReallyBigDataStructure
的引用。
请注意,这是与bytestring代码不同的用例; bytestring代码非常频繁地强制其参数,因此不太可能导致空间泄漏。最好将bytestring代码解释为纯优化,以避免指针解引用。