Reputation: 113
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)
Upvotes: 0
Views: 416
Reputation: 36339
You need this, I think:
frame :: [Entity] -> [EntityInteraction] -> IO ()
frame eList iList = do
parseInteractions iList eList
simulateEntities eList
main :: IO ()
main = do
let entList = [...]
forever $ frame entList []
Though it seems not to make much sense, for example, elist
is always the empty list and so could be omitted.
But if parseInteractions
in your C solution produces/fills eList
, then probably
eList <- parseInteractions iList
In this case, the question is if parseInteractions
really needs to do IO?
Upvotes: 0
Reputation: 3805
It's an interesting phenomenon which only hits you in this empty example.
First, here is a minimal example:
frame :: [a] -> IO ()
frame eList = do
frame (id eList)
main :: IO ()
main = do
frame []
If I run this with runghc
, I get an out of memory error. However, either of these works:
(if you compile them with ghc -O2, you might actually get the output <<loop>>
and the program terminating. runghc
doesn't detect the loop though and you can see the program running in constant space.)
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 []
What's the reason for this? Well, tail recursion per se is not the problem. There is no stack overflow, but an out of memory error. Why, if your lists are not actually changed with every loop iteration?
Well, they are! The function application itself builds up unevaluated thunks because you are never using the values! In all of the working examples, the only difference is that the values are actually evaluated and the thunks removed.
So the sequence of functions calls looks something like this in the erronous example:
frame []
frame (id [])
frame (id (id []))
frame (id (id (id []))) -- the argument takes more memory with every iteration
...
But like this in the working examples:
frame []
frame []
frame []
frame []
frame []
frame [] -- argument stays the same as 'id' is evaluated away.
Even though the thunks are not too expensive for themselves, in an infinite loop they will inevitable eat up all your memory, given enough time.
Upvotes: 5