What is the purpose of the optional atomicModifyIORef result parameter?

The signature of modifyIORef quite simple:

 modifyIORef :: IORef a -> (a -> a) -> IO () 

Unfortunately, this is not thread safe. There is an alternative that refers to this problem:

 atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b 

What is the difference between these two functions? How can I use parameter b when changing an IORef that can be read from another thread?

+8
concurrency haskell
source share
4 answers

As you said in a comment, without concurrency, you could just write something like

 modifyAndReturn ref f = do old <- readIORef ref let !(new, r) = f old writeIORef r new return r 

But in a parallel context, someone else can change the link between reading and writing.

+1
source share

An optional parameter is used to provide a return value. For example, you might want to atomically replace the value stored in IORef and return the old value. You can do it like this:

 atomicModifyIORef ref (\old -> (new, old)) 

If you do not have a return value, you can use the following:

 atomicModifyIORef_ :: IORef a -> (a -> a) -> IO () atomicModifyIORef_ ref f = atomicModifyIORef ref (\val -> (f val, ())) 

which has the same signature as modifyIORef .

+11
source share

This is how I understand it. Think about the functions that follow the parenthesis icon, for example

 withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r 

These functions take a function as an argument and return the return value of this function. atomicModifyIORef similar to this. It takes a function as an argument, and the goal is to return the return value of that function. There is only one complication: the argument function also returns a new value that must be stored in IORef . Because of this, atomicModifyIORef requires this function to return two values. Of course, this case is not exactly analogous to the case of brackets (for example, there is no IO , we are not dealing with exception safety, etc.), but this analogy gives you an idea.

+2
source share

As I like it, use State monad. A state operation changes some internal state and additionally provides an output. Here, the state is inside the IORef , and the result is returned as part of the IO operation. Therefore, we can reformulate the function using State as follows:

 import Control.Monad.State import Data.IORef import Data.Tuple (swap) -- | Applies a stateful operation to a reference and returns its result. atomicModifyIORefState :: IORef s -> State sa -> IO a atomicModifyIORefState ref state = atomicModifyIORef ref (swap . runState state) 
+1
source share

All Articles