Control.Monad.Writer.Strict not as strict as expected

Hello all, I've been debugging a space leak, and believe I've traced it to a chain of unevaluated calls to mappend in the bind operator for Control.Monad.Writer.Strict. I had expected these calls to be evaluated strictly by the strict Writer, but it doesn't seem to be the case. Am I understanding this correctly? If so, is this the intended behavior? Below are two example programs with runtime stats demonstrating the issue. The first uses Control.Monad.Writer.Strict, the second uses an alternative implementation that is strict in the Writer's monoid. The first runs in 78.5s with total memory 630MB. The second runs in 38.15s with total memory 237MB. FWIW, the lazy writer monad turned out to be more efficient for my program (and this example -- it runs in 9.96s with total memory 389MB, and doesn't need an increased stack size). thanks, -matt -- BEGIN writerTest.hs import Control.Monad.Writer.Strict type M = Writer [()] go :: Integer -> M () go 0 = return () go n = return () >> go (n-1) main = print $ runWriter $ go 3000000 -- END writerTest.hs -- BEGIN ./writerTest +RTS -K1G -sstderr ((),[]) 1,521,548,048 bytes allocated in the heap 654,690,520 bytes copied during GC 380,421,720 bytes maximum residency (10 sample(s)) 268,515,200 bytes maximum slop 630 MB total memory in use (0 MB lost due to fragmentation) Generation 0: 1869 collections, 0 parallel, 77.34s, 77.40s elapsed Generation 1: 10 collections, 0 parallel, 0.40s, 0.40s elapsed INIT time 0.00s ( 0.00s elapsed) MUT time 0.76s ( 0.76s elapsed) GC time 77.75s ( 77.81s elapsed) EXIT time 0.00s ( 0.11s elapsed) Total time 78.50s ( 78.56s elapsed) %GC time 99.0% (99.0% elapsed) Alloc rate 2,008,440,129 bytes per MUT second Productivity 1.0% of total user, 1.0% of total elapsed -- END -- BEGIN writerTest2.hs import Data.Monoid data Writer w a = Writer a !w runWriter (Writer a w) = (a, w) instance (Monoid w) => Monad (Writer w) where return a = Writer a mempty m >>= k = case m of Writer a w -> case k a of Writer b w' -> Writer b (w `mappend` w') type M = Writer [()] go :: Integer -> M () go 0 = return () go n = return () >> go (n-1) main = print $ runWriter $ go 3000000 -- END writerTest2.hs -- BEGIN ./writerTest2 +RTS -K1G -sstderr ((),[]) 894,148,696 bytes allocated in the heap 221,145,720 bytes copied during GC 161,143,256 bytes maximum residency (9 sample(s)) 133,479,928 bytes maximum slop 257 MB total memory in use (11 MB lost due to fragmentation) Generation 0: 1185 collections, 0 parallel, 37.57s, 37.60s elapsed Generation 1: 9 collections, 0 parallel, 0.14s, 0.14s elapsed INIT time 0.00s ( 0.00s elapsed) MUT time 0.44s ( 0.44s elapsed) GC time 37.72s ( 37.75s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 38.15s ( 38.18s elapsed) %GC time 98.8% (98.9% elapsed) Alloc rate 2,036,159,030 bytes per MUT second Productivity 1.1% of total user, 1.1% of total elapsed -- END

On Thu, Sep 15, 2011 at 4:43 PM, Matt Brown
Hello all,
I've been debugging a space leak, and believe I've traced it to a chain of unevaluated calls to mappend in the bind operator for Control.Monad.Writer.Strict. I had expected these calls to be evaluated strictly by the strict Writer, but it doesn't seem to be the case. Am I understanding this correctly?
The Strict in Control.Monad.Writer.Strict refers to the fact that the tuple is matched strictly -- not the Monoid.
If so, is this the intended behavior?
The Strict/Lazy writer split has nothing to do with the strictness of your monoid, but rather whether or not operations such as:
fmap f ~(w, a) = (w, f a)
or
fmap f (w, a) = (w, f a)
are used throughout the API. We default to Lazy because slightly more code can terminate (at least in the case of State), lthough some would challenge that due to the behavior in the presence of bottoms the Strict versions are more correct. Neither of these are, or are intended to be, the monad you are looking for. -Edward Kmett

On Thu, 15 Sep 2011, Edward Kmett wrote:
The Strict/Lazy writer split has nothing to do with the strictness of your monoid, but rather whether or not operations such as:
fmap f ~(w, a) = (w, f a)
Doesn't this violate the fmap law fmap id = id because fmap id undefined = (undefined, undefined) ?

Yes. Hence why I pointed out some (myself included) would argue that the lazy versions are not properly monads.
Sent from my iPad
On Sep 15, 2011, at 5:12 PM, Henning Thielemann
On Thu, 15 Sep 2011, Edward Kmett wrote:
The Strict/Lazy writer split has nothing to do with the strictness of your monoid, but rather whether or not operations such as:
fmap f ~(w, a) = (w, f a)
Doesn't this violate the fmap law
fmap id = id
because
fmap id undefined = (undefined, undefined)
?

Thanks for the clarification. Though the lazy version suits my
present need well, I wonder if a variant strict in its monoid would be
useful as well. If nothing else, it might help highlight that the
Strict version is not, something I didn't realize until reading the
source.
-matt
On Thu, Sep 15, 2011 at 2:02 PM, Edward Kmett
On Thu, Sep 15, 2011 at 4:43 PM, Matt Brown
wrote: Hello all,
I've been debugging a space leak, and believe I've traced it to a chain of unevaluated calls to mappend in the bind operator for Control.Monad.Writer.Strict. I had expected these calls to be evaluated strictly by the strict Writer, but it doesn't seem to be the case. Am I understanding this correctly?
The Strict in Control.Monad.Writer.Strict refers to the fact that the tuple is matched strictly -- not the Monoid.
If so, is this the intended behavior?
The Strict/Lazy writer split has nothing to do with the strictness of your monoid, but rather whether or not operations such as:
fmap f ~(w, a) = (w, f a)
fmap f (w, a) = (w, f a) are used throughout the API. We default to Lazy because slightly more code can terminate (at least in the case of State), lthough some would challenge
or that due to the behavior in the presence of bottoms the Strict versions are more correct. Neither of these are, or are intended to be, the monad you are looking for. -Edward Kmett

On Thu, Sep 15, 2011 at 7:50 PM, Matt Brown
Thanks for the clarification. Though the lazy version suits my present need well, I wonder if a variant strict in its monoid would be useful as well. If nothing else, it might help highlight that the Strict version is not, something I didn't realize until reading the source.
FYI- If you need one, you can model a particularly strict writer by using the state monad with the help of seq or ($!). -Edward
participants (3)
-
Edward Kmett
-
Henning Thielemann
-
Matt Brown