如何包装不安全的FFI?(Haskell)

3
这是一篇关于IT技术的翻译。以下是需要翻译的内容:

这是一个关于是否有使用unsafePerformIO的充分理由的跟进问题。

我们知道

p_sin(double *p) { return sin(*p); }

这个函数是不安全的,不能与 unsafePerformIO 一起使用。

但是 p_sin 函数仍然是一个数学函数,它被以不安全的方式实现只是一个实现细节。我们并不希望像矩阵乘法这样需要分配临时内存的操作因此而被放到 IO 中。

我们如何以安全的方式包装这个函数?我们需要进行锁定、自己分配内存等操作吗?是否有处理这个问题的指南/教程?

1个回答

6

实际上,如果您采用那个答案中提到的 p_sin 不安全的方式,它取决于 p_sin 不是 一种从数字到数字的数学函数——它取决于当相同指针指向的内存不同时给出不同的答案。因此,严格来说,在两次调用之间存在某些不同;通过指针的正式模型,我们可能能够确定这种差异。例如:

type Ptr = Int
type Heap = [Double]

p_sin :: Heap -> Ptr -> Double

而 C 函数将等效于:
p_sin h p = sin (h !! p)

结果不同的原因是由于C定义中未命名但隐含的Heap参数不同。

如果p_sin在内部使用临时内存,但不依赖于其接口通过内存的状态,例如。

double p_sin(double x) {
    double* y = (double*)malloc(sizeof(double));
    *y = sin(x);
    x = *y;
    free(y);
    return x;
}

那么我们确实有一个数学函数Double -> Double,我们可以

foreign import ccall safe "p_sin" 
    p_sin :: Double -> Double

我们会没问题的。界面中的指针正在破坏纯净性,而不是C函数。

更实际地说,假设您使用指针实现了一个C矩阵乘法函数,因为这是在C中建模数组的方式。在这种情况下,您可能会扩展抽象边界,所以您的程序中可能会有一些不安全的事情发生,但它们都会隐藏在模块用户之外。在这种情况下,我建议在您的实现中注释所有不安全的内容,并在将其提供给模块用户之前进行IO标记,然后执行unsafePerformIO。这最小化了不纯洁的表面积。

module Matrix
    -- only export things guaranteed to interact together purely
    (Matrix, makeMatrix, multMatrix) 
where

newtype Matrix = Matrix (Ptr Double)

makeMatrix :: [[Double]] -> Matrix
makeMatrix = unsafePerformIO $ ...

foreign import ccall safe "multMatrix" 
   multMatrix_ :: Ptr Double -> IO (Ptr Double)

multMatrix :: Matrix -> Matrix
multMatrix (Matrix p) = unsafePerformIO $ multMatrix_ p

etc.


假设malloc成功(这是一个合理的假设) - nulvinge
@nulvinge,好的。如果malloc失败,那么Haskell所有的纯函数也就不再是纯函数了。 - luqui
怎么会呢?你是在说所有的函数调用都需要内存分配吗? - nulvinge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接