我在Haskell中编写游戏AI,我想在指定的时间内搜索游戏状态树(例如,我希望AI始终花3秒钟来决定采取什么行动)
在像Haskell这样的纯语言中,我该如何实现这一点?我预计我需要深入研究线程等内容,但如果可以的话,我希望尽量减少这方面的使用。
回答:
一个想法:结合timeout
(由@MathematicalOrchid建议)以及来自SafeSemaphore的可变变量,每次你的进程计算出部分结果时存储中间值:
import Control.Monadimport Control.Concurrent.MSampleVarimport System.Timeoutfac :: Integer -> Integerfac 0 = 1fac n = n * fac (n - 1)tmo :: Int -> ((a -> IO ()) -> IO ()) -> IO (Maybe a)tmo ms f = do mvar <- newSV Nothing timeout ms (f (writeSV mvar . (Just $!))) readSV mvarlongComp :: (Integer -> IO ()) -> IO ()longComp save = let loop n = save (fac n) >> loop (n + 1) in loop 0main :: IO ()main = tmo 10000 longComp >>= print
传递给tmo
的函数将其第一个参数作为一个IO
动作,用于保存中间结果。如果超时,最后保存的结果将被返回。结果被转换为WHNF,因此实际计算发生在保存结果的线程中,而不是在从tmo
返回时处理它的线程中。
在这种变体中,传递给tmo
的函数必须保存其输出,而不能返回它。但很容易修改它的签名,使其为(a -> IO ()) -> IO a
。
如果你想保持更纯净的代码,我建议你创建一个自己的单子(monad),来封装这个想法而不让IO
泄露出来。
更新:请注意:
无法保证异常会立即被传递,尽管运行时会努力确保不会发生任意延迟。在GHC中,只有当线程到达安全点时才会引发异常,安全点是指发生内存分配的地方。有些循环在循环内部不执行任何内存分配,因此无法被
throwTo
中断。
(来自throwTo的文档)。在上面的例子中,fac
不分配任何内存,因此对于大数它不会立即被中断。
更新:我基于这些想法创建了一个小型库,该库定义了可以返回部分结果的单子,在返回最终结果之前或在超时时终止。请参见https://github.com/ppetr/timeout-with-results