我正在尝试在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)
答案 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?