I just found this in the GHC 5.04.3 user guide:
When is it safe to use unsafePerformIO?
We’ll give two answers to this question, each of which may be helpful. These criteria are not rigorous in any real sense (you’d need a formal semantics for Haskell in order to give a proper answer to this question), but should give you a feel for the kind of things you can and cannot do with unsafePerformIO.
- It is safe to implement a function or API using unsafePerformIO if you could imagine also implementing the same function or API in Haskell without using unsafePerformIO (forget about efficiency, just consider the semantics).
- In pure Haskell, the value of a function depends only on the values of its arguments (and free variables, if it has any). If you can implement the function using unsafePerformIO and still retain this invariant, then you’re probably using unsafePerformIO in a safe way. Note that you need only consider the observable values of the arguments and result.
For more information, see this thread.
I cannot find this any more in the current user guide. Maybe it should be added back in?
And that mailing list thread also contains this answer from Simon Marlow:
Ok, if we’re going to nit pick
When talking about equality you have
to restrict that to “observable equivalence” in the context of whatever
abstract types you’re dealing with. If a function always returns
observably equivalent results when given observably equivalent
arguments, then it is “safe”.For example, toFoo would not be safe if you can test for equality
between pointers. But if all you can do is convert it back using
fromFoo, then you’re ok.
- It fails to recognise the fact that IO actions have side effects.
Well, side effects are usually only visible in the IO monad so that’s
ok. In your free() example, the result is either () or |, so the
function does depend on more than just the value of the argument. I
think the definition holds (but only just).For example, we normally consider a function which uses some temporary
allocation on the C heap as “safe” to use from unsafePerformIO. But its
side effect is visible from the IO monad by inspecting the C heap
pointer.Cheers,
Simon