`mfix` not working as expected

2019-04-29 23:45发布

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)?

2条回答
Rolldiameter
2楼-- · 2019-04-30 00:14

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.

查看更多
唯我独甜
3楼-- · 2019-04-30 00:18

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.

查看更多
登录 后发表回答