没有办法ForeignPtr
从Data.ByteString
模块外部观察到内部指针的值;它的实现在内部是不纯的,但在外部是纯的,因为它确保只要您在ByteString
构造函数内部看不到,就可以维护需要纯的不变量——这是您看不到的,因为它没有被导出。
这是 Haskell 中的一种常见技术:在底层实现一些不安全的技术,但暴露了一个纯接口;在不影响 Haskell 安全性的情况下,您可以获得不安全技术带来的性能和功耗。(当然,实现模块可能有错误,但你认为如果它是用 C 编写的,它的抽象泄露可能性ByteString
会更小吗?:))
就细微之处而言,如果您是从用户的角度出发,请不要担心:您可以使用 ByteString 和 Vector 库导出的任何函数而无需担心,只要它们不以unsafe
. 它们都是非常成熟且经过良好测试的库,因此您根本不应该遇到任何纯度问题,如果这样做,那是库中的错误,您应该报告它。
至于编写自己的代码以提供外部安全性和不安全的内部实现,规则非常简单:保持引用透明性。
以 ByteString 为例,构造 ByteStrings 的函数用于unsafePerformIO
分配数据块,然后它们对其进行变异并放入构造函数中。如果我们导出构造函数,那么用户代码将能够在ForeignPtr
. 这有问题吗?为了确定是否是,我们需要找到一个纯函数(即不在 in IO
),它可以让我们区分以这种方式分配的两个 ForeignPtr。快速浏览一下文档就会发现有这样一个功能:instance Eq (ForeignPtr a)
让我们区分这些。所以我们不能让用户代码访问ForeignPtr
. 最简单的方法是不导出构造函数。
总结:当你使用不安全的机制来实现某些东西时,验证它引入的杂质不会泄漏到模块之外,例如通过检查你用它产生的值。
就编译器问题而言,您真的不必担心它们;虽然这些功能是不安全的,但它们不应该让你做任何比你在IO
monad 开始时更危险的事情,除了违反纯度。一般来说,如果你想做一些可能会产生真正意想不到的结果的事情,你必须不遗余力地这样做:例如,你可以使用unsafeDupablePerformIO
if 你可以处理两个线程评估相同 thunk 的可能性同时表格unsafeDupablePerformIO m
。unsafePerformIO
略慢于unsafeDupablePerformIO
因为它可以防止这种情况发生。(在使用 GHC 正常执行期间,您的程序中的 Thunk 可以由两个线程同时评估;这通常不是问题,因为两次评估相同的纯值应该没有不利的副作用(根据定义),但是在编写不安全的代码时,这是你必须考虑的事情。)
GHC 文档unsafePerformIO
(和,正如我上面链接的unsafeDupablePerformIO
那样)详细说明了您可能遇到的一些陷阱;类似的文档unsafeCoerce#
(应该通过其可移植名称Unsafe.Coerce.unsafeCoerce 使用)。