Question regarding unsafePerformIO and ForeignPtr

I have something like this.
newDataStruct p = unsafePerformIO ((foreignAllocator p) >>= newForeignPtr foreignFree)
showData d = unsafePerformIO (withForeignPtr d (\d' -> foreignShow d' >>= peekCString))
where foreignAllocator, foreignFree, foreignShow are imported foreign functions. foreignAllocator malloc a chunk of memory, which for my purpose will stay unchanged until it is freed by foreignFree.
foreignShow return a c string representing the data.
According to my knowledge, Haskell’s GC could free the ForeignPtr and the underlying buffer when it detects that there are no more reference to the ForeignPtr. The same memory could later be allotted by the OS when foreignAllocator is called a second time (although how likely this is i don’t know).
This newly created ForeingPtr points to the same address as the previous garbage-collected ForeignPtr, although it may contain different data.
My question is, would showData return the representation of the new data, or would it show the cached representation of the old data?


Using some formatting from nometa’s question and some paraphrasing:

…if by “cached representation” you mean the the stored result, by way of Haskell’s non-strict semantics: maybe. It will depend on the underlying implementation e.g. there are currently two for GHC: single and multi-threaded.

By disguising the use of I/O, you’re telling the Haskell implementation that the result of calling showData is just another regular Haskell expression (i.e. no observable effects), so it’s free to treat that result like any other Haskell expression:

  • if it is a regular Haskell expression, there should be no problems with running it more than once (apart from the extra time required) - the implementation deciding that’s it’s cheaper to recompute:

  • alternately, if there are two or more identical calls of showData in your program:

    ( ... (showData d) ... (showData d) ... )

    there ought to be no problems with the Haskell implementation changing that into:

    let x = showData d in
    ( ... x ... x ... )

Back to the original question:

If your programs are that dependent on such details, you should use unit testing as an “early-warning system”, to lessen the chances of surprises happening in your programs.