
One's intuition would suggest that you could be safely implement mkAtom without wrapping it in a IO monad. After all, at least at a abstract level, an atom table is referentially transparent. The library documentation says that lack of side effects and environmental independance is sufficent to order for uses of unsafePerformIO to be safe. Is there a exact (or at least better) criterion for safety?
An exact criterion would require a formal semantics for Haskell, which we don't have. But informally, if a function which is implemented using unsafePerformIO is pure - that is, its result depends only on the values of its arguments and possibly its free variables - then that is a "safe" use of unsafePerformIO.
"unsafePerformIO" is used in the implementation of mkFastString, so how is it's side effects "safe".
It is safe because the side effects aren't visible outside the implementation of mkFastString. HTH, Simon