How to wrap an unsafe FFI? (Haskell)
This is a question about the next question Is there a good reason to use unsafePerformIO?
So we know that
p_sin(double *p) { return sin(*p); }
is insecure and cannot be used with unsafePerformIO
.
But the function p_sin
is still a mathematical function, the fact that it was implemented in an unsafe way is an implementation detail. We don't want, say, matrix multiplication to be in IO just because it involves temporary memory allocation.
How can we safely use this feature? Do we need to block, allocate memory, etc.? Is there a guide / tutorial for this?
source to share
In fact, if you've included an p_sin
unsafe way in this answer, it depends on a p_sin
non-mathematical function, at least not on numbers to numbers - it depends on providing different answers when memory points to the same thing differently. So, mathematically speaking, there is something different between the two calls; with a formal pointer model we might say. For example.
type Ptr = Int
type Heap = [Double]
p_sin :: Heap -> Ptr -> Double
and then the C function would be equivalent to
p_sin h p = sin (h !! p)
The reason the results will differ is due to a different argument Heap
, which is unnamed but implicit in the definition of C.
If you p_sin
use temporary memory internally, but does not depend on the state of the memory through its interface, for example
double p_sin(double x) {
double* y = (double*)malloc(sizeof(double));
*y = sin(x);
x = *y;
free(y);
return x;
}
then we have an actual math function Double -> Double
and we can
foreign import ccall safe "p_sin"
p_sin :: Double -> Double
and everything will be all right. Pointers in the interface kill cleanliness, not C functions.
Moreover, let's say you have a matrix multiplication function of C implemented with pointers, since this is how you model arrays in C. In this case, you will probably expand the abstraction boundary so that a few unsafe things will happen in your program, but they will all be hidden from the module user. In this case, I recommend annotating everything unsafe IO
in your implementation and then unsafePerformIO
ing it before you pass it to the module user. This minimizes the surface area of the impurity.
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
and etc.
source to share