-- | In rare occasions GHC optimizes innocent looking loops into space-leaking monsters. -- See the discussion here: for more -- details, or -- -- These functions in this module __/might/__ help, at least in conjunction with -- the @-fno-full-laziness@ GHC option. -- -- There is a unit test in the sources of this module, which can be used to do a -- comparative heap profiling of these function vs. their counterparts in the -- @base@ package. -- -- Here are the images of the profiling results, the images show that the -- functions in this module do not leak space, compared to the original functions -- ('Control.Monad.forever' and 'Control.Monad.replicateM_'): -- -- ![Heap profiling of the unit tests called Loop-without-space-leaks shows that less than 200k Bytes were used](docs/extensible-effects-concurrent-test-Loop-without-space-leaks.png) -- ![Heap profiling of the unit tests called Loop-WITH-space-leaks shows that at least 160M Bytes were used](docs/extensible-effects-concurrent-test-Loop-WITH-space-leaks.png) -- -- @since 0.4.0.0 module Control.Eff.Loop ( foreverCheap , replicateCheapM_ ) where import Control.Monad ( void ) -- | A version of 'Control.Monad.forever' that hopefully tricks -- GHC into __/not/__ creating a space leak. -- The intuition is, that we want to do something that is /cheap/, and hence -- should be __recomputed__ instead of shared. -- -- @since 0.4.0.0 {-# NOINLINE foreverCheap #-} foreverCheap :: Monad m => m a -> m () foreverCheap x = x >> foreverCheap (x >> x) -- | A version of 'Control.Monad.replicateM_' that hopefully tricks -- GHC into __/not/__ creating a space leak. -- The intuition is, that we want to do something that is /cheap/, and hence -- should be __recomputed__ instead of shared. -- -- @since 0.4.0.0 {-# NOINLINE replicateCheapM_ #-} replicateCheapM_ :: Monad m => Int -> m a -> m () replicateCheapM_ n e = if n <= 0 then return () else if n == 1 then void e else let nL = n `div` 2 nR = n - nL in replicateCheapM_ nL e >> replicateCheapM_ nR e