问题 严格声明有什么意义?


我正在启动Haskell并且正在查看一些使用“!”定义数据类型的库。 bytestring库中的示例:

data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
                     {-# UNPACK #-} !Int                -- offset
                     {-# UNPACK #-} !Int                -- length

现在我看到了 这个问题 作为对这意味着什么的解释,我想这很容易理解。但我现在的问题是:使用它有什么意义?由于表达式将在需要时进行评估,为什么要强制进行早期评估?

在这个问题的第二个答案C.V.汉森说:“[...]有时懒惰的开销可能太多或浪费”。这是否意味着它用于节省内存(保存值比保存表达式便宜)?

一个解释和一个例子会很棒!

谢谢!

[编辑]我想我应该选择一个没有{ - #UNPACK# - }的例子。所以让我自己做一个。这会有意义吗?是的,为什么以及在什么情况下?

data MyType = Const1 !Int
            | Const2 !Double
            | Const3 !SomeOtherDataTypeMaybeMoreComplex

11661
2018-06-03 19:36


起源

懒惰会对代码产生很大的影响,主要的是懒惰的程序经常在线性空间中运行,而严格的程序需要恒定的空间。但是在你的例子中,真正的原因可能与所讨论的数据有关,这些数据是由不知道如何强制进行惰性求值的外部函数操纵的。 - n.m.
不。 ForeignPtr可以传递给外部函数,当然,但不是结构本身 - 即使使用strict / unpack注释,本机haskell结构也没有定义的ABI - bdonlan


答案:


这里的目标不是严格,而是将这些元素打包到数据结构中。如果没有严格性,那么这三个构造函数参数中的任何一个都可以指向堆分配的值结构或堆分配的延迟评估thunk。严格来说,它只能指向堆分配的值结构。严格要求 和包装结构,可以将这些值内联。

由于这三个值中的每一个都是一个指针大小的实体,并且无论如何都是严格访问的,因此强制使用严格的打包结构可以在使用此结构时保存指针间接。

在更一般的情况下,严格性注释可以帮助减少空间泄漏。考虑这样的情况:

data Foo = Foo Int

makeFoo :: ReallyBigDataStructure -> Foo
makeFoo x = Foo (computeSomething x)

没有严格的注释,如果你只是打电话 makeFoo,它会建立一个 Foo 指向指向的thunk ReallyBigDataStructure,将其保留在内存中,直到某些东西迫使thunk进行评估。如果我们反而拥有

data Foo = Foo !Int

这迫使了 computeSomething 评估立即进行(好吧,一旦有东西强迫makeFoo本身),这就避免了引用 ReallyBigDataStructure

请注意,这是与bytestring代码不同的用例; bytestring代码非常频繁地强制其参数,因此不太可能导致空间泄漏。最好将bytestring代码解释为纯优化以避免指针解引用。


13
2018-06-03 19:43



也许我应该选择一个没有打包/拆包的例子(我不明白/还没看过)。无论如何,对于“指针大小”的实体你是什么意思?我想我对指针有点困惑,因为根据我的理解,Haskell并没有真正的指针。这是某种低级编译器依赖的东西(不是haskell本身的一部分)吗? - o1iver
是的,这是对结构的实际内存表示的低级优化。 - bdonlan
好吧,这是有道理的。但是我编辑了我的答案并添加了另一个例子(^^)。在这种情况下,相同的答案是否有效? - o1iver
更新了答案:) - bdonlan
还有很多情况下编译器会有额外的严格性 能够 证明它,并确实会优化您的代码。 GHC非常聪明,即使不是无所不知(至少在当前版本中)。 - C. A. McCann