In the future, it is polite to try to minimize a little on your own. For example, with a small number of games, I was able to find that the following program also has an overflow stack (with an 8 megabyte stack):
main = print (worker [1..1000] [1..1000])
... which really swallows only that function is spinning you. Let's look at the worker
:
worker (a:[]) prev = prev Set.\\ [a + a] worker (a:as) prev = worker as (prev Set.\\ (map ((+) a) (a:as)))
Even at the first reading, this function was marked in red in my mind, because it is tail-recursive. Haskell's tail recursion is generally not as great an idea as it is in other languages; (where you produce at least one constructor before recursing or recurs a number of times before creating the constructor) is generally better for lazy evaluation. And in fact, what happens here is that every recursive call to worker
creates a deeper and deeper nested thunk in the prev
argument. When the time comes to finally return prev
, we need to go very deep into the long chain of calls to Set.\\
to work out what we finally had.
This problem is a bit confused due to the fact that the obvious strictest annotation does not help. Give a massage to the worker
until it works. The first observation is that the first sentence is completely subordinate to the second. This is a style; it should not affect behavior (except for empty lists).
worker [] prev = prev worker (a:as) prev = worker as (prev Set.\\ map (a+) (a:as))
Now an obvious annotation of strict severity:
worker [] prev = prev worker (a:as) prev = prev `seq` worker as (prev Set.\\ map (a+) (a:as))
I was surprised to find that this is still a stack overflow! The point is that seq
in lists evaluates far enough to find out if the list matches either []
or _:_
. The stack overflow is not performed below:
import Control.DeepSeq worker [] prev = prev worker (a:as) prev = prev `deepseq` worker as (prev Set.\\ map (a+) (a:as))
I have not included the final version back to the source code, but at least working with a minimized main
above. By the way, you might like the following implementation idea, which also overflows the stacks:
import Control.Monad worker as bs = bs Set.\\ liftM2 (+) as as
but which can be fixed using Data.Set
instead of Data.List
, and no strictness annotations:
import Control.Monad import Data.Set as Set worker as bs = toList (fromList bs Set.\\ fromList (liftM2 (+) as as))