I am trying to work out the async model of Haskell and have troubles matching the known concepts against what Haskell does. I have the following code:
module Main where
import Control.Concurrent.Async (concurrently)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
(a, b) <- concurrently (pure $ myFibonaci 45) (pure $ myFibonaci 42)
print a
putStrLn "----"
print b
myFibonaci :: Integer -> Integer
myFibonaci 0 = 0
myFibonaci 1 = 1
myFibonaci n = myFibonaci (n - 1) + myFibonaci (n - 2)
Not sure why but logs are printed in the following order:
267914296 -- value of a
----
And only then it starts computing the value of b
267914296
----
102334155 -- value of b
I am a little puzzled since concurrently should in my understanding spawn 2 green threads which could in turn be mapped to different system threads and end up executing on different cores. Meaning that by the time I get the value of a the value of b should have theoretically (if scheduling delays are not due) be computed. I am running code with -threaded GHC options so this shouldn't be the problem. Is this a case of Haskell lazyness? I suspect the better solution would be to use Control.Parallel but what is the difference between async and Control.Parallel then? I am pretty sure Go which uses a similar model doesn't make such distinctions
This is due to lazyness. The IO action you are executing is pure, not myFibonaci xy, therefore at the line (a, b) <- ... both elements of the tuple are thunks which are evaluated when printing. Use @jonpurdy's solution: $!. Two things to consider:
Control.Parallel API, will force you to choose which "level of lazyness" you want for your parallel execution, via rseq, rdeepseq, etc..below you have the fix to your code and the strategies approach
import Control.Concurrent.Async (concurrently)
import Data.Time.Clock (getCurrentTime)
import Control.Exception
import Control.Parallel.Strategies
-- just an utility
measure :: Show a => a -> IO ()
measure a = do
getCurrentTime >>= print
print a
getCurrentTime >>= print
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
-- if you change $! by $ you'll notice measure b is much longer because
-- computation is done on measure b but with $!, both measure are the same
-- since the calculation is done here
(a, b) <- concurrently (pure $! myFibonaci 15) (pure $! myFibonaci 35)
measure a
putStrLn "----"
measure b
putStrLn "********"
getCurrentTime >>= print
-- strategies interface force you to choose the level of lazyness
-- |- eval in IO in parallel
-- | |- this pure computation
-- | | |- with strategy "parallel tuples with full evaluation of each side of the tuple"
result <- usingIO (myFibonaci 14, myFibonaci 34) (evalTuple2 rdeepseq rdeepseq)
print result
getCurrentTime >>= print
myFibonaci :: Integer -> Integer
myFibonaci 0 = 0
myFibonaci 1 = 1
myFibonaci n = myFibonaci (n - 1) + myFibonaci (n - 2)
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