Let's say we want a collection of values that are computed on demand (the computation has some side effects), and not recomputed again when you access them again.
The following two naive approaches will not work:
Prelude> let f x = print x >> return x
Prelude> let a = map f "abc"
Prelude> :t a
a :: [IO Char]
Prelude> head a
'a'
'a'
Prelude> head a
'a'
'a'
Prelude> let b = mapM f "abc"
Prelude> :t b
b :: IO [Char]
Prelude> fmap head b
'a'
'b'
'c'
'a'
Prelude> fmap head b
'a'
'b'
'c'
'a'
How do I do it correctly? As of now, I don't even understand what type the collection should have.
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.
λ>
λ> 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With