Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performing side effects during computation only once in Haskell

Tags:

haskell

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.

like image 207
zabolekar Avatar asked Aug 22 '20 11:08

zabolekar


Video Answer


2 Answers

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.

like image 168
chi Avatar answered Oct 23 '22 06:10

chi


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.

like image 40
jpmarinier Avatar answered Oct 23 '22 06:10

jpmarinier