在 Haskell 中实现 setTimeout 函数是否存在或者会有多大的麻烦?
JavaScript 的 setTimeout 和 Qt 的 QTimer 等其他类似方法通常在单个线程中的单个事件循环中工作(不包括 web worker 或 QThread)。使用 Haskell 对确切的行为进行建模有点困难,尽管并非不可能,因为 GHC 已经提供了(内部)implementation。
如果您不关心实际的单线程行为(这也意味着具有几乎相同超时的两个函数可能同时执行),那么您可以简单地分叉一个新线程并延迟其操作:
doLater :: Int -> IO () -> IO ThreadId
doLater ms io = forkIO $ threadDelay (ms * 1000) >> io
对于任何进一步的想法,我们实际上需要更多地了解您的特定事件循环。
这样的模式在 Haskell 上看起来如何?
非常,非常概括,你需要这样的东西:
mkGame :: World
-> (Input -> World -> World)
-> (TimeDiff -> World -> World)
-> (World -> GraphicalRepresentation)
-> IO ()
mkGame initialWorld handleInput handleProgression drawWorld = undefined
请注意,您可能会在其中添加其他参数,例如每秒世界更新的最大数量。
我们现在如何建模 setTimeout?假设World 类似于:
data World = World {
getWorldData :: WorldData,
getCurrentTime :: TimePoint, -- would get updated by your loop
getTimeouts :: Queue TimePoint (World -> World)
}
其中Queue 是任何工作优先级队列(pick one, 有很多,或者自己构建)。现在您可以使用
简单地设置超时
setTimeout world delay action = world { getTimeouts = timeouts' }
where timeouts' = insert (getTimeouts world) t action
t = delay + getCurrentTime world
-- insert :: Queue p v -> p -> v -> Queue p v
如果您希望能够取消超时,您还需要Queue 上的密钥,查看GHC.Event.PSQ 以获得灵感。
除此之外,您现在可以检查时间是否已过,并通过简单地遍历队列并应用您的所有函数来在您的游戏循环中采取相应的行动。
这基本上是一个非常简单粗暴的基础,您可以用它来激发您自己的工作。根据您实际想要做的事情,您可能想看看gloss,它已经实现了一个非常相似的概念,虽然没有超时,但在这种情况下,您仍然可以将超时和总时间差添加到您的世界,只需使用合适的更新功能(TimeDiff -> World -> World 部分)。