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

时间:2013-08-09 06:17:26

标签: loops haskell main

我正在尝试在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中使用可变状态执行主循环的方法是什么?

(我已经在C编程作为业余爱好4年了,我才开始学习haskell)

2 个答案:

答案 0 :(得分:5)

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

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

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

即使th th不是太昂贵,但在无限循环中,如果有足够的时间,它们将不可避免地占用你所有的记忆。

答案 1 :(得分:0)

你需要这个,我想:

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?