I expected the following code to first prompt start:
, then wait for a user response, before echoing the previous user response back, and awaiting a new one:
import System.IO (hFlush, stdout)
import Control.Monad.Fix (mfix)
f :: [String] -> IO [String]
f = mapM $ \x -> putStr x >> putStr ": " >> hFlush stdout >> getLine
g x = f ("start":x)
main = mfix g
But it gives the error thread blocked indefinitely in an MVar operation
after the first line is entered.
Why is this and how can I fix it (excuse the pun)?
The reason why this can't work is that in mfix f
runs any effect in f
exactly once. This follows from the tightening rule
mfix (\x -> a >>= \y -> f x y) = a >>= \y -> mfix (\x -> f x y)
in particular
mfix (\x -> a >> f x) = a >> mfix f
for any correct instance of MonadFix
. So the fixed point is only computed for the pure (lazily computed) value inside the monadic action, not for the effects. In your case using mfix
asks for printing/reading characters just once in such a way that the input is equal to the output, which is impossible. This isn't a proper use case for mfix
. You'd use mfix
with IO
for example to construct a cyclic data structure in IO
like in these examples.
In your case you should use iterateM_
or something similar rather than mfix
. see also iterate + forever = iterateM? Repeating an action with feedback.
Unfortunately, mfix
in the IO
monad doesn't really work to produce lists piecemeal like that. This is because most actions in the IO
monad are very strict: they don't produce any part of their result until the whole action has been performed. In particular, mapM
in IO
will not return any part of its result list until it has gone through all of its input list, which leaves mfix
with no hope of tying the knot in the right way here.
In general, mfix
in IO
really only works if the tied-back value isn't looked at strictly until after the whole mfix
action is completed. This still has some possible uses, like initializing a data structure with cycles of mutable cells using only newIORef
.