我希望以下代码首先提示start:
,然后等待用户响应,然后回复之前的用户响应,然后等待新的响应:
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
但是在输入第一行之后它会给出错误thread blocked indefinitely in an MVar operation
。
为什么会这样,我该如何解决它(原谅双关语)?
答案 0 :(得分:7)
这不能起作用的原因是mfix f
在f
中只运行一次效果。这遵循紧缩规则
mfix (\x -> a >>= \y -> f x y) = a >>= \y -> mfix (\x -> f x y)
特别是
mfix (\x -> a >> f x) = a >> mfix f
对于MonadFix
的任何正确实例。因此,仅针对monadic动作中的纯(延迟计算)值计算固定点,对于效果,不。在您的情况下,使用mfix
要求只输入一次打印/读取字符,使输入等于输出,这是不可能的。这不是mfix
的正确用例。您可以使用mfix
与IO
一起构建IO
中的循环数据结构,例如these examples。
在您的情况下,您应该使用iterateM_
或类似的内容,而不是mfix
。另见iterate + forever = iterateM? Repeating an action with feedback。
答案 1 :(得分:4)
不幸的是,mfix
monad中的IO
并没有像这样零碎地生成列表。这是因为IO
monad中的大多数操作都非常严格:在完成整个操作之前,他们不会生成任何部分的结果。特别是,mapM
中的IO
将不会返回其结果列表的任何部分,直到它通过所有输入列表,这使得mfix
无法将结结合在一起。正确的方式在这里。
一般情况下,mfix
中的IO
实际上只有在整个mfix
操作完成后才严格查看并列值才有效。这仍然具有一些可能的用途,例如仅使用newIORef
初始化具有可变单元循环的数据结构。