由于ByteString
是ForeignPtr
的构造函数:
data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
{-# UNPACK #-} !Int -- offset
{-# UNPACK #-} !Int -- length
如果我有一个返回ByteString
的函数,那么给定一个输入,比如一个常量Word8
,该函数将返回一个具有非确定性的ForeignPtr值的ByteString - 该值将是什么由内存管理员决定。
那么,这是否意味着返回ByteString的函数不纯?如果您使用了ByteString和Vector库,那么这似乎并非如此。当然,如果是这样的话,它将被广泛讨论(并希望在谷歌搜索之上显示)。这种纯度是如何实施的?
提出这个问题的原因是我很好奇在GHC编译器的角度来看,使用ByteString和Vector对象有什么微妙之处,在构造函数中给出了ForeignPtr成员。
答案 0 :(得分:18)
无法从ForeignPtr
模块外部观察Data.ByteString
内指针的值;它的实现是内部不纯,但外部纯,因为它确保只要你在ByteString
内看不到纯粹的不变量就会被维护构造函数 - 你不能,因为它没有导出。
这是Haskell中的常用技术:在引擎盖下使用不安全技术实现某些东西,但暴露出纯粹的接口;在不影响Haskell安全性的前提下,您可以获得性能和功耗不安全技术。 (当然,实现模块可能有错误,但是如果它是用C语言编写的话,你认为ByteString
会更少可能泄漏它的抽象吗?:))
就微妙的观点而言,如果你是从用户的角度谈论,不要担心:你可以使用ByteString和Vector库导出的任何函数而不用担心,只要它们不以{开头} {1}}。它们都是非常成熟且经过良好测试的库,所以你不应该遇到任何纯度问题,如果你做,这就是库中的一个错误,你应该报告它。 / p>
至于使用不安全的内部实现编写自己的代码以提供外部安全性,规则非常简单:维护引用透明度。
以ByteString为例,构造ByteStrings的函数使用unsafe
来分配数据块,然后将它们变异并放入构造函数中。如果我们导出了构造函数,那么用户代码就可以获得unsafePerformIO
。这有问题吗?为了确定它是否,我们需要找到一个纯函数(即不在ForeignPtr
中),它允许我们区分以这种方式分配的两个ForeignPtrs。快速浏览the documentation表明有这样的功能:IO
让我们区分这些功能。因此,我们不得允许用户代码访问instance Eq (ForeignPtr a)
。最简单的方法是不导出构造函数。
总结:当您使用不安全的机制来实现某些功能时,请确认它引入的杂质不会泄漏到模块外部,例如:通过检查你用它产生的值。
就编译器问题而言,你不应该真的担心它们;虽然这些功能不安全,但它们不应该让你做任何比ForeignPtr
monad开始时更加危险的事情,而不是违反纯度。一般来说,如果你想做一些可能会产生真正意外结果的事情,你就不得不这样做了:例如,你可以使用unsafeDupablePerformIO
处理两个线程同时评估格式IO
的同一个thunk的可能性。 unsafeDupablePerformIO m
比unsafePerformIO
略慢,因为它可以防止这种情况发生。 (在使用GHC正常执行期间,程序中的thunks可以通过两个线程同时评估;这通常不是问题,因为两次评估相同的纯值应该没有不利的副作用(根据定义),但是在编写不安全的代码时,这是你必须考虑的事情。)
GHC documentation for unsafePerformIO
(以及unsafeDupablePerformIO
,正如我上面所述)详述了您可能遇到的一些陷阱;类似于unsafeCoerce#
的文档(应该通过其可移植名称Unsafe.Coerce.unsafeCoerce使用)。