Performing side effects during computation only once in Haskell
Here's a small library to cache values using an IORef
. The function cache
below transforms an IO a
into another IO a
which will only perform the side effects once, and return the cached value the next times it is executed.
import Data.IORef
cache :: IO a -> IO (IO a)
cache action = do
v <- newIORef Nothing
return $ do
x <- readIORef v
case x of
Nothing -> do
res <- action
writeIORef v $ Just res
return res
Just res -> return res
This is your function:
f :: Char -> IO Char
f x = print x >> return x
Here's a small example showing how to use cache
.
main :: IO ()
main = do
acts <- mapM (cache . f) "abc"
-- here we have acts :: [IO Char]
putStrLn "1st: "
head acts -- prints 'a'
putStrLn "2nd: "
head acts -- prints nothing
putStrLn "3rd: "
head acts -- prints nothing
return ()
Only the first call prints 'a'
on screen.
Slightly unorthodox solution:
λ>
λ> import System.IO.Unsafe
λ>
λ> let { cache2 :: IO a -> IO a ; cache2 act = return (unsafePerformIO act) }
λ>
λ> :t cache2
cache2 :: IO a -> IO a
λ>
λ> let f x = print x >> return x
λ> let a = map f "abc"
λ>
λ> :t a
a :: [IO Char]
λ>
λ>
λ> a2 = map cache2 a
λ>
λ> :t a2
a2 :: [IO Char]
λ>
Testing:
λ>
λ> head a2
'a'
'a'
λ>
λ> head a2
'a'
λ>
λ> head a2
'a'
λ>
λ>
λ> a2s = sequence a2
λ>
λ> :t a2s
a2s :: IO [Char]
λ>
λ> a2s
"a'b'
b'c'
c"
λ>
λ> a2s
"abc"
λ>
Explanation: It is occasionally claimed that function unsafePerformIO :: IO a -> a offers a way to extract a value from the IO monad. The (sometimes omitted) catch is: how does the Haskell runtime system preserve referential transparency then ? well, simply by running the underlying computation just once, saving the result and discarding possible side effects of running the computation several times.
Edit:
As mentioned by Jon Purdy in the comments, in a general multithreaded context function unsafeInterleaveIO :: IO a -> IO a has to be preferred to unsafePerformIO
. See the documentation for details.