使用可变状态在haskell中实现主循环



我正试图在Haskell中实现一个主循环,在C中我会这样写:

EntityInteraction *frame(Entity *eList, EntityInteraction *iList) {
    parseInteractions(eList, iList);
    return simulateEntities(eList);
}
int main() {
    Entity eList[] = {...}
    EntityInteraction *iList = NULL;
    while(true) {
        iList = frame(eList, iList);
    }
}

因此,我试图在haskell中通过使框架成为递归函数来复制这一点:

frame :: [Entity] -> [EntityInteraction] -> IO ()
frame eList iList = do
    frame (parseInteractions iList eList) (simulateEntities eList)
main :: IO ()
main = do
    let entList = [...]
    frame entList []

但这只会导致预期的堆栈溢出,所以我的问题是,在haskell中使用可变状态进行主循环的propper方法是什么?

(作为一种爱好,我已经用C编程4年了,我刚刚开始学习haskell)

这是一个有趣的现象,只有在这个空例子中才会出现。

首先,这里有一个最小的例子:

frame :: [a] -> IO ()
frame eList = do    
    frame (id eList) 
main :: IO ()
main = do        
    frame [] 

如果使用runghc运行此操作,则会出现内存不足错误。然而,这两项工作中的任何一项:(如果你用ghc-O2编译它们,你可能会得到输出<<loop>>,程序终止。不过runghc没有检测到循环,你可以看到程序在恒定的空间中运行。)

A)

frame :: [a] -> IO ()
frame eList = do   
    frame eList
main :: IO ()
main = do        
    frame [] 

B)

frame :: [a] -> IO ()
frame eList = do    
    print eList
    frame (id eList) 
main :: IO ()
main = do        
    frame [] 

C)

frame :: [a] -> IO ()
frame eList = do   
    eList `seq` frame (id eList) 
main :: IO ()
main = do        
    frame [] 

这是什么原因?好吧,尾递归本身不是问题所在。没有堆栈溢出,而是内存不足错误。为什么,如果您的列表实际上并没有随着每次循环迭代而改变?

是的!函数应用程序本身会构建未求值的thunk,因为您从未使用过这些值!在所有的工作示例中,唯一的区别是实际评估了值,并删除了thunks。

因此,函数调用序列在错误的例子中看起来像这样:

frame []
frame (id [])
frame (id (id []))
frame (id (id (id []))) -- the argument takes more memory with every iteration
...

但在工作示例中是这样的:

frame []
frame []
frame []
frame []
frame []
frame []                -- argument stays the same as 'id' is evaluated away.

尽管这些暴徒对他们自己来说并不太昂贵,但在一个无限的循环中,只要有足够的时间,他们将不可避免地吞噬你所有的记忆。

我想你需要这个:

frame :: [Entity] -> [EntityInteraction] -> IO ()
frame eList iList = do
    parseInteractions iList eList
    simulateEntities eList
main :: IO ()
main = do
    let entList = [...]
    forever $ frame entList []

尽管这似乎没有多大意义,例如,elist总是空列表,因此可以省略。但是,如果您的C解决方案中的parseInteractions产生/填充eList,那么可能是

eList <- parseInteractions iList

在这种情况下,问题是parseInteractions是否真的需要进行IO?

相关内容

  • 没有找到相关文章