
2009/10/21 Gregory Crosswhite
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
This is indeed wrong, but not how you think it is. The code you pass to unsafePerformIO has type Num a => IO a, so the value passed to return has type Num a. So foo has type Num a too and its value is 1. Exactly like in mkNext = do ref <- newIORef 0 modifyIORef ref (+1) readIORef ref which is a complicated way to write mkNext = return 1 Now, it's clear that foo has value 1 and printing it three times should output three 1. The whole point of having mkNext return an action (that should be called next, and not foo, as it is much clearer) in previous code was too be able to execute it multiple times and having it return a new value. In general, expecting print bar print bar print bar outputing three different things is wrong, as bar should be pure. If bar is not pure, then it should be a <- bar print a b <- bar print b c <- bar print c Cheers, Thu