I recently read about the space leak issue of the Writer/WriterT monad. If I correctly understood this problem, it is because the bind operator, i.e. (>>=) is not tail-recursive:
m >>= f = WriterT $ do
(a, w1) <- runWriterT m
(b, w2) <- runWriterT (f a)
return (b, w1 `mappend` w2)
With the definition of WriterT and Writer:
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
type Writer w = WriterT w Identity
I am curious about whether introducing laziness on the second parameter of mappend would solve this space-leak problem.
By introducing laziness, I mean something like the (++) operator:
(++) :: [a] -> [a] -> [a]
(++) [] ys = ys
(++) (x:xs) ys = x : (xs ++ ys)
The result is produced without actually touching the second parameter.
Now, if we use a monad m with lazy monadic bind (e.g. m ~ Identity, which gives us the plain old Writer monad), and use mappend mentioned above, then the f a part (w2) can remain a thunk while evaluating mappend w1 w2, thus the result can be partially consumed (w1) without actually forcing the rest expression (w2).
Am I correct on this? Are space leaks avoided in such Writer monads?