3

This is a followup question to Is there ever a good reason to use unsafePerformIO?

So we know that

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

is unsafe, and cannot be used with unsafePerformIO.

But the p_sin function is still a mathematical function, the fact that it was implemented in an unsafe way is an implementation detail. We don't exactly want, say, matrix multiplication to be in IO just because it involves allocating temporary memory.

How can we wrap this function in a safe way? Do we need to lock, allocate memory ourselves, etc? Is there a guide/tutorial for dealing with this?

asked Aug 22, 2014 at 5:22

1 Answer 1

6

Actually, if you incorporate the way p_sin is unsafe from that answer, it depends on p_sin not being a mathematical function, at least not one from numbers to numbers -- it depends on giving different answers when the memory the same pointer points to is different. So, mathematically speaking, there is something different between the two calls; with a formal model of pointers we might be able to tell. E.g.

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 would differ is because of a different Heap argument, which is unnamed but implicit in the C definition.

If p_sin used temporary memory internally, but did not depend on the state of memory through its interface, e.g.

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

then we do have an actual mathematical function Double -> Double, and we can

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

and we're be fine. Pointers in the interface are killing the purity here, not C functions.

More practically, let's say you have a C matrix multiplication function implemented with pointers, since that's how you model arrays in C. In this case you'd probably expand the abstraction boundary, so there would be a few unsafe things going on in your program, but they would all be hidden from the module user. In this case, I recommend annotating everything unsafe with IO in your implementation, and then unsafePerformIOing right before you give it to the module user. This minimizes the surface area of 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

etc.

answered Aug 22, 2014 at 6:34
Sign up to request clarification or add additional context in comments.

3 Comments

This is assuming malloc succeeds (which is a reasonable assumption to make)
@nulvinge, sure. If malloc is failing then none of Haskell's pure functions are pure either.
how so? Are you saying all function calls require memory allocations?

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.