2

The following seems to work (as in: it keeps saying Surely tomorrow every second)

import Control.Concurrent
import Control.Concurrent.MVar

import Control.Exception (evaluate)

main :: IO ()
main = do
    godot <- newEmptyMVar
    forkIO $ do
        g <- evaluate $ last [0..]
        putMVar godot g
    let loop = do
        threadDelay $ 10^6
        g <- tryTakeMVar godot
        case g of
            Just g -> return ()
            Nothing -> putStrLn "Surely tomorrow." >> loop
    loop

This uses evaluate to ensure last [0..] is actually forced to WHFN before filling the MVar – if I change the forked thread to

    forkIO $ do
        let g = last [0..]
        putMVar godot g

then the program terminates.

However, evaluate uses seq. In the context of deterministic parallelism, it's always emphasized that seq is not sufficient to actually guarantee evaluation order. Does this problem not arise in a monadic context, or should I better use

    forkIO $ do
        let g = last [0..]
        g `pseq` putMVar godot g

to ensure the compiler can't reorder the evaluation so tryTakeMVar succeeds prematurely?

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319

2 Answers2

1

If I'm not totally wrong, evaluating last [0..] to WHNF would take an infinite amount of time, because WHNF for an Int means that you know the exact number.

putMVar will not start executing before last [0..] is evaluated to WHNF (which as we know takes forever), because putMVar will need the RealWorld-token (s) returned by the call to evaluate. (Or to put it more simply: evaluate works. It finishes only after evaluating its argument to WHNF.)

evaluate :: a -> IO a
evaluate a = IO $ \s -> seq# a s
--                     this    ^

putMVar (MVar mvar#) x = IO $ \ s# ->
--           which is used here ^^
    case putMVar# mvar# x s# of
--         is needed here ^^
        s2# -> (# s2#, () #)

where seq# is a GHC-prim function that guarantees to return (# a, s #) only after evaluating a to WHNF (that's its purpose). That is, only after a is evaluated to WHNF, s can be used in the call to putMVar. Although these tokens are purely imaginative ("RealWorld is deeply magical..."), they are respected by the compiler, and the whole IO-monad is built on top of it.

So yes, evaluate is enough in this case. evaluate is more than seq: it combines IO-monadic sequencing with seq#-sequencing to produce its effect.


In fact, the pseq version looks a bit fishy to me, because it ultimately depends on lazy, where evaluate ultimately depends on seq# and monadic token-passing. And I trust seq# a bit more.

Michael
  • 6,451
  • 5
  • 31
  • 53
1

The point of pseq is to ensure that after the parent thread sparks a computation with par, it does not immediately proceed to try to evaluate the result of the sparked computation itself, but instead does its own job first. See the documentation for an example. When you're working more explicitly with concurrency, you shouldn't need pseq.

dfeuer
  • 48,079
  • 5
  • 63
  • 167