Mutable References
Table of Contents
1 Mutable References
1.1 IORef - Mutable References Inside IO Monad
1.1.1 Overview
The IORef type provides mutable references in the IO Monad. It allows Haskell to perform computations with mutability like imperative languages.
Module: Data.IORef
Function / Type | Signature | Description | |
---|---|---|---|
Types | |||
data IORef a | |||
Functions | |||
newIORef | :: | a -> IO (IORef a) | Create new IORef |
readIORef | :: | IORef a -> IO a | Read IORef |
writeIORef | :: | IORef a -> a -> IO () | Update IORef |
modifyIORef | :: | IORef a -> (a -> a) -> IO () | Apply a function to the value in the IORef and update it. |
1.1.2 Examples
1.1.2.1 Simple IORef example in Haskell REPL:
{- ===== Check The Types ====== -} > import Data.IORef > > :info IORef newtype IORef a = GHC.IORef.IORef (GHC.STRef.STRef GHC.Prim.RealWorld a) -- Defined in ‘GHC.IORef’ instance Eq (IORef a) -- Defined in ‘GHC.IORef’ > > :t newIORef newIORef :: a -> IO (IORef a) > > :t readIORef readIORef :: IORef a -> IO a > > :t modifyIORef modifyIORef :: IORef a -> (a -> a) -> IO () > {- === Experiment 1 - IORef within the REPL ========== -} {- | Extract IO a -> a is only possible in the REPL due to it be inside an IO monad. -} > cell <- newIORef (100.0 :: Double) > :t cell cell :: IORef Double > > readIORef cell 100.0 > > :t readIORef cell readIORef cell :: IO Double > -- Apply a function to value returned from reading the cell --------------------------------------------------------------- > fmap ((+)100) $ readIORef cell 200.0 > > (*3) <$> ((+)100) <$> readIORef cell 600.0 > > (+5) <$> (*3) <$> ((+)100) <$> readIORef cell 605.0 > > (+5) . (*3) . ((+)100) <$> readIORef cell 605.0 > > fmap ((+5) . (*3) . ((+)100)) $ readIORef cell 605.0 > -- Update ------------------------------------------------------------ > :t writeIORef writeIORef :: IORef a -> a -> IO () > > writeIORef cell 500.322 > readIORef cell 500.322 > > fmap ((+5) . (*3) . ((+)100)) $ readIORef cell 1805.966 > -- Modify ---------------------------------------------------------- > writeIORef cell 200.0 > readIORef cell 200.0 > > :t modifyIORef modifyIORef :: IORef a -> (a -> a) -> IO () > > modifyIORef cell (\x -> 3.0 * x ) > readIORef cell 600.0 > modifyIORef cell (\x -> 3.0 * x ) > readIORef cell 1800.0 > modifyIORef cell (\x -> 3.0 * x ) > readIORef cell 5400.0 >
Example: Counter.
import Data.IORef :{ counter :: IO (IORef Int) counter = newIORef 0 :} :{ incrCounter :: IORef Int -> IO () incrCounter cell = modifyIORef cell (\x -> x + 1) :} :{ showCounter cell = do value <- readIORef cell putStrLn $ "The counter value is " ++ show value :} > :t showCounter showCounter :: Show a => IORef a -> IO () > > counter >>= showCounter The counter value is 0 > :{ testCounter1 c = do incrCounter c incrCounter c incrCounter c showCounter c :} > :t testCounter1 testCounter1 :: IORef Int -> IO () > > testCounter1 =<< counter The counter value is 3 > > showCounter =<< counter The counter value is 0 > > counter >>= showCounter The counter value is 0 > > c <- counter > :t c c :: IORef Int > > showCounter c The counter value is 0 > incrCounter c > showCounter c The counter value is 1 > > incrCounter c >> showCounter c The counter value is 2 > incrCounter c >> showCounter c The counter value is 3 > incrCounter c >> showCounter c The counter value is 4 > incrCounter c >> showCounter c The counter value is 5 > incrCounter c >> showCounter c The counter value is 6 > incrCounter c >> showCounter c The counter value is 7 > incrCounter c >> showCounter c The counter value is 8 >
1.1.2.2 Stateful Function
import qualified Data.IORef as IORef :{ makeCounter :: Int -> IO (IO Int) makeCounter init = do c <- IORef.newIORef init let incCounter = do a <- IORef.readIORef c IORef.writeIORef c (a + 1) return a return incCounter :} > counter <- makeCounter 0 counter :: IO Int > counter 0 it :: Int > counter 1 it :: Int > counter 2 it :: Int > counter 3 it :: Int > counter 4 it :: Int > counter 5 it :: Int > counter 6 it :: Int >
1.1.2.3 Example: Sum list elements in a imperative way using IORef.
import Data.IORef import Control.Monad (forever, forM_) :{ sumList :: Num a => [a] -> IO a sumList xs = do acc <- newIORef 0 forM_ xs $ \x -> modifyIORef acc (\val -> val + x) result <- readIORef acc return result :} > :t sumList sumList :: Num a => [a] -> IO a > > sumList [1, 2, 3, 4, 5, 6] 21 > sumList [1, 2, 3, 4, 5, 6, 10] 31 > sumList [1, 2, 3, 4, 5, 6, 10, 11] 42 > > sumList [1, 2, 3.23, 4.0, 5, 6, 10, 11] 42.230000000000004 > :set +s --- SumList simplified --- :{ sumList :: Num a => [a] -> IO a sumList xs = do acc <- newIORef 0 forM_ xs $ \x -> modifyIORef acc (\val -> val + x) readIORef acc :} > sumList [1, 2, 3, 4, 5, 6] 21 > sumList [1, 2, 3.34, 4.7567, 5, 6] 22.0967 > {- | ========== Testing Space Leaks ============= -} -- > sumList [1..10000] 50005000 (0.03 secs, 3,770,264 bytes) > > sumList [1..10000000] 50000005000000 (6.70 secs, 3,696,483,880 bytes) > -- WARNING: Don't try it or the computer may freeze because it consumes a huge amount of memory -- > sumList [1..1000000000] fSegmentation fault -- Stop with linux emergency keys - Ctrl + SysRq + f -- Solution to the space leak issue: Use the strict version of modifyIORef ( modifyIORef') -- :{ sumList' :: Num a => [a] -> IO a sumList' xs = do acc <- newIORef 0 forM_ xs $ \x -> modifyIORef' acc (\val -> val + x) result <- readIORef acc return result :} > sumList' [1..10000] 50005000 (0.03 secs, 3,120,456 bytes) -- This implementation is faster. > sumList' [1..10000000] 50000005000000 (3.64 secs, 3,040,086,304 bytes) >
Example: Ask the number for a number and display the sum in a infinite loop.
import Data.IORef import Control.Monad (forever, forM_) :{ sumLoop :: IO () sumLoop = do cell <- newIORef (0 :: Int) forever $ do putStr "Enter the next number to sum: " nextNum <- fmap read getLine :: IO Int modifyIORef cell (\value-> value + nextNum) sumval <- readIORef cell putStrLn $ "sum = " ++ show sumval :} > sumLoop Enter the next number to sum: 100 sum = 100 Enter the next number to sum: 200 sum = 300 Enter the next number to sum: 300 sum = 600 Enter the next number to sum: 45 sum = 645 Enter the next number to sum: 78 sum = 723 Enter the next number to sum: -458 sum = 265 Enter the next number to sum: 200 sum = 465 Enter the next number to sum: 300 sum = 765 Enter the next number to sum: ^CInterrupted. --- Type Ctrl+C to exit the Loop >
1.1.2.4 Infinite loop with delay
import Data.IORef import Control.Concurrent (threadDelay) import Control.Monad (forever) delay1Second = 1000000 -- 1 million us = 1 second :{ runLoop = do count <- newIORef 0 forever $ do val <- readIORef count putStrLn $ "Counter is " ++ show val modifyIORef count (+1) threadDelay delay1Second :} > runLoop Counter is 0 Counter is 1 Counter is 2 Counter is 3 Counter is 4 Counter is 5 Counter is 6 Counter is 7 Counter is 8 Counter is 9 Counter is 10 Counter is 11 Counter is 12 Counter is 13 Counter is 14 Counter is 15 Counter is 16 Counter is 17 ... ... ...
1.1.2.5 Simulating objects with closures
The code below simulates an object counter that has an internal integer state. The functions or IO actions incrementCounter, decrementcounter and getCounter simulate the object's methods.
import qualified Data.IORef as IORef :{ data Counter = Counter { incrementCounter :: IO () , decrementCounter :: IO () , getCounter :: IO Int } :} :{ newCounter :: IO Counter newCounter = do counter <- IORef.newIORef 0 return $ Counter { incrementCounter = IORef.modifyIORef counter (+1) , decrementCounter = IORef.modifyIORef counter (+(-1)) , getCounter = IORef.readIORef counter } :}
Running:
> counter <- newCounter counter :: Counter > getCounter counter 0 it :: Int > > incrementCounter counter it :: () > getCounter counter 1 it :: Int > incrementCounter counter it :: () > getCounter counter 2 it :: Int > incrementCounter counter >> getCounter counter 3 it :: Int > incrementCounter counter >> getCounter counter 4 it :: Int > > decrementCounter counter >> getCounter counter 3 it :: Int > decrementCounter counter >> getCounter counter 2 it :: Int > decrementCounter counter >> getCounter counter 1 it :: Int > decrementCounter counter >> getCounter counter 0 it :: Int >
1.2 ST Monad - Mutable References Inside Pure Functions
1.2.1 Overview
The ST monad is used to perform local mutability. The mutability happens only inside the function.
Modules:
Functions:
Function | Signature | Description | |
---|---|---|---|
Module Data.STRef | |||
newSTRef | :: | a -> ST s (STRef s a) | Create a new STRef in the current state thread |
readSTRef | :: | STRef s a -> ST s a | Read the value of an STRef |
writeSTRef | :: | STRef s a -> a -> ST s () | Write a new value into an STRef |
modifySTRef | :: | STRef s a -> (a -> a) -> ST s () | Mutate the contents of an STRef. |
Module Control.Monad.ST | |||
runST | :: | (forall s. ST s a) -> a | Extract content |
Monad and Functor signatures | |||
(>>=) | :: | ST s a -> (a -> ST s b) -> ST s b | Monad bind operator |
return | Monad return function | ||
(>>) | :: | ST s a -> ST s b -> ST s b | Monad (>>) operator |
fmap | :: | (a -> b) -> ST s a -> ST s b | Functor operator |
1.2.2 Examples
Example 1: Sum all list elements.
import Control.Monad.ST import Data.STRef import Control.Monad (forM_) :{ sumST :: Num a => [a] -> a sumST xs = runST $ do -- Extract value from ST Monad n <- newSTRef 0 -- Create an STRef mutable reference (aka variable) forM_ xs $ \x -> do -- For each element of xs, add it to n modifySTRef n (+x) readSTRef n -- read value from mutable reference n and return it :} > > sumST [1, 2, 3, 4, 5, 6] 21 > sum [1, 2, 3, 4, 5, 6] 21 > > sumST [1..100] 5050 > > sumST [1..1000000] 500000500000 >
Example: Alternative fold implementation in a imperative-like way.
:{ foldST :: (acc -> x -> acc) -> acc -> [x] -> acc foldST step acc0 xs = runST $ do accum <- newSTRef acc0 forM_ xs $ \x -> modifySTRef accum (\a -> step a x) readSTRef accum :} > foldl (\acc x -> 10*acc + x) 0 [1, 2, 3, 4, 5] 12345 > > foldl (\acc x -> 10*acc + x) 0 [1, 2, 3, 4, 5, 6] 123456 > > foldST (\acc x -> 10*acc + x) 0 [1, 2, 3, 4, 5] 12345 > foldST (\acc x -> 10*acc + x) 0 [1, 2, 3, 4, 5, 6] 123456 >
It is equivalent to the code below in Python. This function is a pure fucntion like foldST despite it has local mutability.
def foldl(step, acc0, xs): accum = acc0 for x in xs: accum = step(accum, x) return accum >>> def foldl(step, acc0, xs): ... accum = acc0 ... for x in xs: ... accum = step(accum, x) ... return accum ... >>> >>> foldl(lambda a, x: 10 * a + x, 0, [1, 2, 3, 4, 5]) 12345 >>> >>> foldl(lambda a, x: 10 * a + x, 0, [1, 2, 3, 4, 5, 6]) 123456 >>>