【问题标题】:Implement main loop in haskell using mutable state使用可变状态在haskell中实现主循环
【发布时间】:2013-08-11 01:15:53
【问题描述】:

我正在尝试在 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);
    }
}

所以我试图通过使 frame 成为递归函数来在 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)

【问题讨论】:

  • 这样的尾递归不应导致堆栈溢出。也许你 elistilist 在某个时候变得太大了?另外,你是如何编译你的程序的?尝试使用ghc -O2 以确保。
  • eList,并且 iList 永远不会被添加到。我尝试使用 -O2 开关,但它仍然给我消息:“main:内存不足”
  • 也许你可以提供一个完整的、可编译的、最小的例子。我刚试过main = frameframe = return () >> frame,虽然这显然会烧毁 cpu,但它在恒定空间中快乐地永远运行(使用 800k 内存)。
  • 完整的源代码在这里:pastebin.com/FrnyLb4e 它是一个功能纯实体系统,但目前它什么都不做
  • 问题是你只是永远递归而没有输出(“观察”)任何东西。在递归调用frame 之前添加print,您会发现它运行良好。 (懒惰确实改变了“可变”状态的性质——即你正在构建 thunk 而不是计算任何东西——但你走在正确的轨道上,观察结果会有所帮助)

标签: loops haskell main


【解决方案1】:

我认为你需要这个:

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?

【讨论】:

    【解决方案2】:

    这是一个有趣的现象,只有在这个空洞的例子中才会引起你的注意。

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

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

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

    一)

    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.
    

    即使 thunk 对它们自己来说并不算太昂贵,但在无限循环中,如果有足够的时间,它们将不可避免地吃掉你所有的内存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-01
      • 1970-01-01
      • 2022-08-18
      • 1970-01-01
      • 1970-01-01
      • 2018-03-14
      相关资源
      最近更新 更多