Как обернуть небезопасные FFI? (Haskell)
Это дополнительный вопрос к тому, есть ли когда-нибудь веская причина для использования unsafePerformIO?
Итак, мы знаем, что
p_sin(double *p) { return sin(*p); }
небезопасно и не может быть использовано с unsafePerformIO
,
Но p_sin
Функция по-прежнему является математической функцией, а тот факт, что она была реализована небезопасным способом, является деталью реализации. Скажем, мы не хотим, чтобы умножение матриц происходило в IO только потому, что оно связано с выделением временной памяти.
Как мы можем обернуть эту функцию безопасным способом? Нужно ли нам самим блокировать, выделять память и т. Д.? Есть ли руководство / учебное пособие для решения этой проблемы?
1 ответ
На самом деле, если вы включите способ p_sin
небезопасно от этого ответа, это зависит от p_sin
не является математической функцией, по крайней мере, не одной из чисел в числах - это зависит от предоставления разных ответов, когда память, на которую указывает тот же указатель, различна. Итак, математически говоря, между этими двумя вызовами есть что-то другое; с формальной моделью указателей мы могли бы сказать. Например
type Ptr = Int
type Heap = [Double]
p_sin :: Heap -> Ptr -> Double
и тогда функция C будет эквивалентна
p_sin h p = sin (h !! p)
Причина, по которой результаты будут отличаться, заключается в том, что Heap
аргумент, который является неназванным, но неявным в определении C.
Если 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. В этом случае вы, вероятно, расширили бы границу абстракции, поэтому в вашей программе будет происходить несколько небезопасных вещей., но все они будут скрыты от пользователя модуля. В этом случае я рекомендую комментировать все небезопасно с 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
и т.п.