14

由于 aByteString是一个构造函数ForeignPtr

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

如果我有一个返回的函数ByteString,然后给定一个输入,比如一个常量Word8,该函数将返回一个具有非确定性 ForeignPtr 值的 ByteString - 至于该值将由内存管理器确定。

那么,这是否意味着返回 ByteString 的函数不是纯函数?如果您使用过 ByteString 和 Vector 库,显然情况并非如此。当然,如果是这样的话,它会被广泛讨论(并希望出现在谷歌搜索之上)。这种纯度是如何执行的?

问这个问题的原因是我很好奇使用 ByteString 和 Vector 对象所涉及的微妙点,从 GHC 编译器的角度来看,给定构造函数中的 ForeignPtr 成员。

4

1 回答 1

18

没有办法ForeignPtrData.ByteString模块外部观察到内部指针的值;它的实现在内部是不纯的,但在外部是纯的,因为它确保只要您在ByteString构造函数内部看不到,就可以维护需要纯的不变量——这是您看不到的,因为它没有被导出。

这是 Haskell 中的一种常见技术:在底层实现一些不安全的技术,但暴露了一个纯接口;在不影响 Haskell 安全性的情况下,您可以获得不安全技术带来的性能和功耗。(当然,实现模块可能有错误,但你认为如果它是用 C 编写的,它的抽象泄露可能性ByteString更小吗?:))

就细微之处而言,如果您是从用户的角度出发,请不要担心:您可以使用 ByteString 和 Vector 库导出的任何函数而无需担心,只要它们不以unsafe. 它们都是非常成熟且经过良好测试的库,因此您根本不应该遇到任何纯度问题,如果这样做,那是库中的错误,您应该报告它。

至于编写自己的代码以提供外部安全性和不安全的内部实现,规则非常简单:保持引用透明性

以 ByteString 为例,构造 ByteStrings 的函数用于unsafePerformIO分配数据块,然后它们对其进行变异并放入构造函数中。如果我们导出构造函数,那么用户代码将能够在ForeignPtr. 这有问题吗?为了确定是否是,我们需要找到一个函数(即不在 in IO),它可以让我们区分以这种方式分配的两个 ForeignPtr。快速浏览一下文档就会发现有这样一个功能:instance Eq (ForeignPtr a)让我们区分这些。所以我们不能让用户代码访问ForeignPtr. 最简单的方法是不导出构造函数。

总结:当你使用不安全的机制来实现某些东西时,验证它引入的杂质不会泄漏到模块之外,例如通过检查你用它产生的值。

就编译器问题而言,您真的不必担心它们;虽然这些功能是不安全的,但它们不应该让你做任何比你在IOmonad 开始时更危险的事情,除了违反纯度。一般来说,如果你想做一些可能会产生真正意想不到的结果的事情,你必须不遗余力地这样做:例如,你可以使用unsafeDupablePerformIOif 你可以处理两个线程评估相同 thunk 的可能性同时表格unsafeDupablePerformIO munsafePerformIO略慢于unsafeDupablePerformIO因为它可以防止这种情况发生。(在使用 GHC 正常执行期间,您的程序中的 Thunk 可以由两个线程同时评估;这通常不是问题,因为两次评估相同的纯值应该没有不利的副作用(根据定义),但是在编写不安全的代码时,这是你必须考虑的事情。)

GHC 文档unsafePerformIO(和,正如我上面链接的unsafeDupablePerformIO那样)详细说明了您可能遇到的一些陷阱;类似的文档unsafeCoerce#(应该通过其可移植名称Unsafe.Coerce.unsafeCoerce 使用)。

于 2011-12-23T14:02:27.740 回答