Как обернуть небезопасные 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

и т.п.

Другие вопросы по тегам