I would like to use IO Int
to represent a stream of integers by hiding an IORef
in its definition:
tickrate :: Int
tickrate = 20000
ioIntTest :: Int -> IO Int
ioIntTest i0 = do
intRef <- newIORef i0
f intRef
where
f :: IORef Int -> IO Int
f ref = do
i <- readIORef ref
modifyIORef ref ( 1)
return i
ioTest :: Int -> IO ()
ioTest n = do
let intStream = ioIntTest n
intStreamToPrint intStream
where
intStreamToPrint is = do
threadDelay tickrate
c <- is
putStrLn (show c)
intStreamToPrint is
However, if I call ioTest n
, rather than seeing an increasing list of numbers printed to the screen, I see only the starting number, n
, repeating indefinitely.
While I could refactor this code so that incrementing and reading the value of ioIntTest i0
are done separately, I would like to know if/why the following is impossible:
Can I make an IO Int
such that each time it is used in (>>=)
(either explicitly or implicitly in do
notation) the returned Int
mutates?
While such an IO Int
is perhaps not referentially transparent, I thought that was the point of wrapping computations in the IO
monad.
Such a refactoring could be:
tickrate :: Int
tickrate = 20000
ioIntMutate :: IORef Int -> IO Int
ioIntMutate ref = do
i <- readIORef ref
modifyIORef ref ( 1)
return i
ioTest :: Int -> IO ()
ioTest n = do
intStream <- newIORef n
intStreamToPrint intStream
where
intStreamToPrint is = do
threadDelay tickrate
c <- ioIntMutate is
putStrLn (show c)
intStreamToPrint is
In other words, is there any way to replace the line ioIntMutate is
in the third-to-last line with an IO Int
?
CodePudding user response:
You can use IO (IO Int)
for that. Like this:
ioIntTest :: Int -> IO (IO Int)
ioIntTest n = do
ref <- newIORef n
pure $ do
i <- readIORef ref
writeIORef ref (i 1)
pure i
ioTest :: Int -> IO ()
ioTest n = do
intStream <- ioIntTest n
intStreamToPrint intStream
where
intStreamToPrint is = do
threadDelay tickrate
c <- is
putStrLn (show c)
intStreamToPrint is
Note that the only difference between my ioTest
and your ioTest
is this line:
let intStream = ioIntTest n -- yours
intStream <- ioIntTest n -- mine
And, by the way, this solution is not so contrived. I have used a trick like this before to hide internal implementation details of an async RPC channel; or for another example on Hackage, check out once
. You don't need to know whether that's implemented with IORef
s or some other trick, and the author can switch tricks as they find better ones.
As a stylistic note, I'd write ioTest
a little differently. One of these two:
ioTest :: Int -> IO ()
ioTest n = do
intStream <- ioIntTest n
forever (intStream >>= print >> threadDelay tickrate)
-- OR
forever $ do
intStream >>= print
threadDelay tickrate