I'm trying to learn Haskell and I'm playing around with IORef to which I try to save and find records. My code looks something like this (note that I've chosen "String" as IORef type in this example only for convienence and briefty, in my actual code I'm using a record. And also ignore that I'm using a Set instead of a Map, I will change that):
module MyTest where
import Data.IORef
import Data.Set
import Data.Foldable (find)
type State = (Set String)
type IORefState = IORef State
saveStringToState :: IO IORefState -> String -> IO String
saveStringToState stateIO string = do
state <- stateIO
atomicModifyIORef
state
(\oldStrings ->
let updatedStrings = insert string oldStrings
in (updatedStrings, updatedStrings))
stringsState <- readIORef state :: IO State
putStrLn ("### saved: " ++ show stringsState)
return string
findStringInState :: IO IORefState -> String -> IO (Maybe String)
findStringInState stateIO soughtString = do
state <- stateIO :: IO IORefState
strings <- readIORef state :: IO State
putStrLn ("Looking for " ++ soughtString ++ " in: " ++ show strings)
return $ find (== soughtString) strings
doStuff =
let stateIO = newIORef empty
in do saveStringToState stateIO "string1"
findStringInState stateIO "string1"
What I want to achieve is to share the state (Set) between the two function calls so that findStringInState
can return the String
that I just inserted into the Set. But when I run the doStuff
function I get this:
*MyTest> doStuff
### saved: fromList ["string1"]
Looking for string1 in: fromList []
Nothing
I've probably misunderstood something since I thought the IORef should indeed be the container for my state.
Seems that you confused IO IORefState
with IORefState
(without IO
), more generally, IO a
with a
.
In your case, the value of IO IORefState
is the action newIORef empty
, which represents "an action that creates a fresh new IORef
from scratch".
By contrast, IORefState
(without IO
) is the right, raw object that you should share among the functions that use it (saveStringToState
, and findStringInState
).
Then, both saveStringToState
and findStringInState
separately call newIORef empty
, i.e. each of them create a different IORefState
object, which cannot be affected by the other.
To fix, you must call newIORef empty
(as an IO
action using <-
) in doStuff
function and share the IORefState
created by newIORef empty
instead of IO IORefState
:
saveStringToState :: IORefState -> String -> IO String
...
findStringInState :: IORefState -> String -> IO (Maybe String)
...
let stateIO = newIORef empty
in do ioRef <- stateIO
saveStringToState ioRef "string1"
findStringInState ioRef "string1"
-- Or, more simply:
do ioRef <- newIORef empty
saveStringToState ioRef "string1"
findStringInState ioRef "string1"
In my opinion, the difference between IO a
with a
is similar to the difference between "a function object that returns a value typed as a
(with some side effects)" and "just a raw value typed as a
" in the other programming languages.