如果你只想绘制连续的状态,那就很简单了。首先,使用你的step
函数和初始状态,使用 iterate
函数。然后,iterate step initialState
是一个(无限)列表,包含每个模拟状态。随后,你可以将display
映射到其中,以获取绘制每个状态的IO操作,因此你会得到以下代码:
allStates :: [SimState]
allStates = iterate step initialState
displayedStates :: [IO ()]
displayedStates = fmap display allStates
运行它最简单的方式是使用 intersperse
函数 在每个显示操作之间添加一个“延迟”动作,然后使用 sequence_
函数 运行整个过程:
main :: IO ()
main = sequence_ $ intersperse (delay 20) displayedStates
当然这意味着你必须强制终止应用程序,并且排除了任何交互,所以一般来说这不是一个好的方法。
更明智的方法是在每个步骤中交错进行诸如“查看应用程序是否应该退出”之类的事情。您可以使用显式递归来完成:
runLoop :: SimState -> IO ()
runLoop st = do display st
isDone <- checkInput
if isDone then return ()
else delay 20 >> runLoop (step st)
我更喜欢编写非递归步骤,然后使用更抽象的循环组合器。不幸的是,在标准库中没有很好的支持这种方式,但它看起来应该是这样的:
runStep :: SimState -> IO SimState
runStep st = do display st
delay 20
return (step st)
runLoop :: SimState -> IO ()
runLoop initialState = iterUntilM_ checkInput runStep initialState
留下实现iterUntilM_
函数的任务供读者完成, 呵呵。
monad-loops
包,我认为这实际上是该方法最清晰的演示。 - C. A. McCannListT
的关系大致相同,就像Data.List
中的递归组合器与普通列表的关系一样;同样,它们强调递归和最终结果,而流处理则强调中间步骤的方面。我认为理解每个方面可以更好地了解正在发生的事情。 - C. A. McCann