Home > OS >  Global state with IORef, why doesn't this work?
Global state with IORef, why doesn't this work?

Time:01-22

For the sake of understanding I played around with IORef and tried to come up with something that would be close to having a global, mutable state:

import Data.IORef

x :: IO (IORef Int)
x = newIORef 0

incrAndPrint :: IO ()
incrAndPrint = do z <- x
                  modifyIORef z ( 1)
                  readIORef z >>= putStrLn . show

main :: IO ()
main = incrAndPrint >> incrAndPrint

However, to my surprise this prints

1
1

not

1
2

Could someone explain why? Moreover, is there a way to make this 'work'? If not, why?

CodePudding user response:

Your x is an IO action that creates a new IORef Int so when you use it it will always create a new one starting at 0. You can easily make this work by increment the same reference both times:

incrAndPrint :: IORef Int -> IO ()
incrAndPrint z = do
    modifyIORef z ( 1)
    readIORef z >>= print

main = do
    z <- x
    incrAndPrint z
    incrAndPrint z

If you really, really have to use a global mutable state you can do it with unsafePerformIO, which works with GHC because it will make sure the IORef isn't accessed before it's initialized and the NOINLINE prevents a new reference to be created wherever you use it.

import Data.IORef
import System.IO.Unsafe

x :: IORef Int
x = unsafePerformIO $ newIORef 0
{-# NOINLINE x #-}

incrAndPrint :: IO ()
incrAndPrint = do
    modifyIORef x ( 1)
    readIORef x >>= putStrLn . show

main :: IO ()
main = incrAndPrint >> incrAndPrint
  • Related