As in my previous question, I\'m trying to wrap the Data.Binary.Put monad into another monad so that later I can ask it questions like \"how many bytes it\'s going to write\
It looks like the monad transformer is too lazy. You can create a heap profile (without having to build it specially) by running the program with:
$ ./myprog +RTS -hT
$ hp2ps myprog.hp
$ open hp2ps.ps # Or whichever viewer you have
In this case it's not particularly helpful, because it only shows lots of PAP
s, FUN_1_0
s and FUN_2_0
s. This means the heap is made up of lots of partially applied functions, and functions of one argument and two arguments. This usually means that something is not evaluated enough. Monad transformers are somewhat notorious for this.
The workaround is to use a more strict monad transformers using continuation passing style. (his requires {-# LANGUAGE Rank2Types #-}
.
newtype MyStateT s m a =
MyStateT { unMyStateT :: forall r. (s -> a -> m r) -> s -> m r }
Continuation passing style means that instead of returning a result directly, we call another function, the continuation, with our result, in this case s
and a
. The instance definitions look a bit funny. To understand it read the link above (Wikipedia).
instance Monad m => Monad (MyStateT s m) where
return x = MyStateT (\k s -> k s x)
MyStateT f >>= kk = MyStateT (\k s ->
f (\s' a -> unMyStateT (kk a) k s') s)
runMyStateT :: Monad m => MyStateT s m a -> s -> m (a, s)
runMyStateT (MyStateT f) s0 = f (\s a -> return (a, s)) s0
instance MonadTrans (MyStateT s) where
lift act = MyStateT (\k s -> do a <- act; k s a)
type Out = MyStateT Integer P.PutM ()
Running it now gives constant space (the "maximum residency" bit):
$ ./so1 +RTS -s
begin
end
8,001,343,308 bytes allocated in the heap
877,696,096 bytes copied during GC
46,628 bytes maximum residency (861 sample(s))
33,196 bytes maximum slop
2 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 14345 collections, 0 parallel, 3.32s, 3.38s elapsed
Generation 1: 861 collections, 0 parallel, 0.08s, 0.08s elapsed
The downside of using such strict transformers is that you can no longer define MonadFix
instances and certain laziness tricks no longer work.