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?

+3


source to share


1 answer


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.

+5


source







All Articles