And just because this has not been explicitly stated: it's not just for aesthetic reasons that you couldn't do this with a pure function, but because it violates the semantics and gets you the wrong result. So for example, if you modified Tim's code to be
import Data.IORef
import System.IO.Unsafe
mkNext :: (Num a) => IO a
mkNext = do
ref <- newIORef 0
return . unsafePerformIO $
do
modifyIORef ref (+1)
readIORef ref
main :: IO ()
main = do
foo <- mkNext
print foo
print foo
print foo
Then the output that you will see (with GHC at least) is
1
1
1
because the compiler assumes that it only needs to evaluate foo once, after which it can cache the result due to assumed referential transparency.
- Greg