To get acquainted with unsafePerformIO (how to use it and when to use it), I've implemented a module for generating unique values.
Here's what I have:
module Unique (newUnique) where
import Data.IORef
import System.IO.Unsafe (unsafePerformIO)
-- Type to represent a unique thing.
-- Show is derived just for testing purposes.
newtype Unique = U Integer
deriving Show
-- I believe this is the Haskell'98 derived instance, but
-- I want to be explicit, since its Eq instance is the most
-- important part of Unique.
instance Eq Unique where
(U x) == (U y) = x == y
counter :: IORef Integer
counter = unsafePerformIO $ newIORef 0
updateCounter :: IO ()
updateCounter = do
x <- readIORef counter
writeIORef counter (x+1)
readCounter :: IO Integer
readCounter = readIORef counter
newUnique' :: IO Unique
newUnique' = do { x <- readIORef counter
; writeIORef counter (x+1)
; return $ U x }
newUnique :: () -> Unique
newUnique () = unsafePerformIO newUnique'
To my delight, the package called Data.Unique chose the same datatype as I did; on the other hand, they chose the type newUnique :: IO Unique, but I want to stay out of IO if possible.
Is this implementation dangerous? Could it possibly lead GHC to change the semantics of a program which uses it?
Treat
unsafePerformIOas a promise to the compiler. It says "I promise that you can treat this IO action as if it were a pure value and nothing will go wrong". It's useful because there are times you can build a pure interface to a computation implemented with impure operations, but it's impossible for the compiler to verify when this is the case; insteadunsafePerformIOallows you to put your hand on your heart and swear that you have verified that the impure computation is actually pure, so the compiler can simply trust that it is.In this case that promise is false. If
newUniquewere a pure function thenlet x = newUnique () in (x, x)and(newUnique (), newUnique ())would be equivalent expressions. But you would want these two expressions to have different results; a pair of duplicates of the sameUniquevalue in one case, and a pair of two differentUniquevalues in the other. With your code, there's really no way to say what either expression means. They can only be understood by considering the actual sequence of operations the program will carry out at runtime, and control over that is exactly what you're relinquishing when you useunsafePerformIO.unsafePerformIOsays it doesn't matter whether either expression is compiled as one execution ofnewUniqueor two, and any implementation of Haskell is free to choose whatever it likes each and every time it encounters such code.