生成ByteString(或任何具有ForeignPtr组件的对象)的函数的纯度

Sal*_*Sal 14 haskell bytestring

由于a ByteString是一个构造函数ForeignPtr:

data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
                     {-# UNPACK #-} !Int                -- offset
                     {-# UNPACK #-} !Int                -- length
Run Code Online (Sandbox Code Playgroud)

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

那么,这是否意味着返回ByteString的函数不纯?如果您使用了ByteString和Vector库,那么这似乎并非如此.当然,如果是这样的话,它将被广泛讨论(并希望在谷歌搜索之上显示).这种纯度是如何实施的?

提出这个问题的原因是我很好奇从GHC编译器的角度来看,使用ByteString和Vector对象有什么微妙之处,在构造函数中给出了ForeignPtr成员.

ehi*_*ird 18

无法ForeignPtrData.ByteString模块外部观察指针的值; 它的实现是内部不纯的,但外部是纯粹的,因为它确保只要你不能在ByteString构造函数内部看到所需的纯变量,你不能,因为它没有被导出.

这是Haskell中的一种常见技术:在引擎盖下实现不安全技术,但暴露出纯粹的接口; 在不影响Haskell安全性的前提下,您可以获得性能和功耗不安全技术.(当然,实现模块可能有错误,但如果它是用C语言写的,你认为ByteString不太可能泄漏它的抽象吗?))

至于微妙的观点,如果你是从用户的角度谈论,不要担心:你可以使用ByteString和Vector库导出的任何函数而不用担心,只要它们不开始unsafe.它们都是非常成熟且经过良好测试的库,所以你不应该遇到任何纯度问题,如果你这样做,那就是库中的一个错误,你应该报告它.

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

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

总结:当您使用不安全的机制来实现某些功能时,请确认它引入的杂质不会泄漏到模块外部,例如通过检查您使用它生成的值.

至于编译器问题,你不应该真的担心它们; 虽然这些功能是不安全的,但它们不应该让你做任何更危险的事情,而不是违反纯度,而不是你在IOmonad中做的事情.一般来说,如果你想要做的事,可能产生真正意想不到的效果,你必须走出自己的方式来做到这一点:例如,你可以使用unsafeDupablePerformIO,如果你能处理两个线程的评估相同的thunk的可能性形式unsafeDupablePerformIO m同时.unsafePerformIO稍微慢一点,unsafeDupablePerformIO因为它可以防止这种情况发生.(在使用GHC正常执行期间,程序中的thunks可以通过两个线程同时评估;这通常不是问题,因为两次评估相同的纯值应该没有不利的副作用(根据定义),但是在编写不安全的代码时,这是你必须考虑的事情.)

为GHC文件unsafePerformIO(和unsafeDupablePerformIO,正如我上面的链接)的细节,你可能会遇到一些陷阱; 类似的文档unsafeCoerce#(应该通过其便携式名称,Unsafe.Coerce.unsafeCoerce使用).