I have an algorithm that generates a search tree:
data SearchTree a = Solution a | Contradiction | Search [ SearchTree a ] deriving (Show, Functor)
The algorithm generates this tree lazily. I also identified a simple evaluator, which in fact is only the first search for depth.
simpleEval :: MonadPlus m => SearchTree a -> ma simpleEval (Solution a) = return a simpleEval Contradiction = mzero simpleEval (Search ps) = foldr mplus mzero $ map simpleEval ps
I noticed that many of the solutions that my algorithm produces look like the following search tree:
nest :: Int -> SearchTree a -> SearchTree a nest 0 = id nest n = nest (n-1) . Search . (:[]) tree0 = Search ts where ts = cycle $ t0 : replicate 100 t1 ++ [t2] t0 = nest 100 $ Solution 'a' t1 = nest 1000 $ Contradiction t2 = nest 4 $ Solution 'b'
Namely, they have many very deep branches without solutions, several deep branches with a solution and very few small branches with a solution. Based on this, I decided that I want another appraiser who will โgive upโ on branches that are too deep. Name it cutoffEval . cutoffEval 5 tree0 should find only b , because for this there are infinitely many branches of depth less than 5, and they contain only b s. I implemented it like this:
cutoff :: (MonadPlus m) => Int -> SearchTree a -> (ma, [SearchTree a]) cutoff cu = go cu where plus ~(m0, l0) ~(m1, l1) = (mplus m0 m1, l0 ++ l1) zero = (mzero, []) go 0 x = (mzero, [x]) go _ Contradiction = zero go _ (Solution a) = (return a, []) go d (Search ps) = foldr plus zero $ map (go $ d-1) ps cutoffEval :: MonadPlus m => Int -> SearchTree a -> ma cutoffEval cu = go where go t = case cutoff cu t of (r,ts) -> foldr mplus mzero $ r : map go ts
But this function creates a huge space leak compared to simpleEval :
putStrLn $ take 4000 $ simpleEval tree0 -- 2MB residency putStrLn $ take 4000 $ cutoffEval 10 tree0 -- 600MB residency
Profiling shows that almost all selection happens in cutoff.go ; and most of the highlighting is due to the mysterious type main:Tree.sat_s5jg and constructor (,) . It seems to me because of irrefutable templates, tuple constructors are built as thunks, and not forced plus ; and, as a rule, the solution to the problem of space leakage should make your function more strict, but here the removal of irrefutable templates causes cutoff hang, so I can not do this.
I tested this with GHC 7.6, 7.8 and 7.10. A problem was discovered in each of them.
So my questions are: is it possible to write a cutoffEval to work in a constant space like simpleEval ? And in general, how can I fix a space leak if I cannot make my implementation more strict, because the algorithm depends on it?