0

Haskell である種のメイン ループを実装しようとしていますが、CI では次のように記述します。

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 を学び始めたばかりです)

4

2 に答える 2

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 [] 

ハ)

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

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

これの理由は何ですか?まあ、末尾再帰自体は問題ではありません。スタック オーバーフローはありませんが、メモリ不足エラーです。ループの繰り返しごとにリストが実際に変更されないのはなぜでしょうか?

まあ、そうです!値を使用していないため、関数アプリケーション自体が未評価のサンクを構築します! すべての実際の例で、唯一の違いは、値が実際に評価され、サンクが削除されることです。

したがって、関数呼び出しのシーケンスは、誤った例では次のようになります。

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.

サンク自体はそれほど高価ではありませんが、無限ループでは、十分な時間があれば、必然的にすべてのメモリを使い果たします。

于 2013-08-09T07:41:13.693 に答える