0

I'm trying to implement a main loop of sorts in Haskell, in C I would write it like this:

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);
    }
}

So I attempted to replicate this in haskell by making frame a recursive function:

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

main :: IO ()
main = do
    let entList = [...]
    frame entList []

But this just results in a stack overflow as expected, so my question is what is the propper way to do a main loop in haskell that uses mutable state?

(I've been programming in C as a hobby for 4 years and I'm just starting to learn haskell)

4

2 回答 2

5

这是一个有趣的现象,只有在这个空洞的例子中才会打动你。

首先,这是一个最小的示例:

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

main :: IO ()
main = do        
    frame [] 

如果我使用 运行它runghc,我会收到内存不足错误。但是,其中任何一个都有效:(如果您使用 ghc -O2 编译它们,您实际上可能会得到输出<<loop>>并且程序终止。runghc虽然没有检测到循环,但您可以看到程序在恒定空间中运行。)

一个)

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

main :: IO ()
main = do        
    frame [] 

二)

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,因为您从不使用这些值!在所有工作示例中,唯一的区别是实际评估了值并删除了 thunk。

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

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.

尽管 thunk 本身并不太贵,但在无限循环中,如果有足够的时间,它们将不可避免地吃掉你的所有内存。

于 2013-08-09T07:41:13.693 回答
0

你需要这个,我认为:

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

main :: IO ()
main = do
    let entList = [...]
    forever $ frame entList []

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

eList <- parseInteractions iList

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

于 2013-08-09T08:35:56.373 回答