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 inf
exactly once. This follows from the tightening rulein particular
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 usingmfix
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 formfix
. You'd usemfix
withIO
for example to construct a cyclic data structure inIO
like in these examples.In your case you should use
iterateM_
or something similar rather thanmfix
. see also iterate + forever = iterateM? Repeating an action with feedback.Unfortunately,
mfix
in theIO
monad doesn't really work to produce lists piecemeal like that. This is because most actions in theIO
monad are very strict: they don't produce any part of their result until the whole action has been performed. In particular,mapM
inIO
will not return any part of its result list until it has gone through all of its input list, which leavesmfix
with no hope of tying the knot in the right way here.In general,
mfix
inIO
really only works if the tied-back value isn't looked at strictly until after the wholemfix
action is completed. This still has some possible uses, like initializing a data structure with cycles of mutable cells using onlynewIORef
.