suggestions for improving MonadWriter

Petr P petr.mvd at
Mon Jan 28 18:10:43 CET 2013


2013/1/28 Edward A Kmett <ekmett at>

> I've often wanted to remove the Monoid constraint, so I'm a strong +1 on
> the first one.
> The second one I'm -1 on, given that it can be expressed with the existing
> combinators and due in small part to a more ideological concern:
> Over time I've come to view cramming pass/listen and local into the
> respective MonadWriter and MonadReader classes as a mistake. If we had the
> hierarchy to do over, I'd probably want them split into separate
> subclasses. This would permit more instances involving Cont, logging to
> disk, etc.

I strongly support your idea. Currently, MonadWriter and MonadReader just
reflect ReaderT and WriterT so that we can make monad stacks. But they
don't reflect the ideas - a monad we write to or read from, without any
additional constraints. I'd really love to have them split up: A more
general class that has just 'tell' and 'writer', and another that also has
'listen' and 'pass' (perhaps also 'contained').
I tried to discuss it in haskell-cafe once <
haskell-cafe at>, but without any feedback on the
idea, so I thought everybody is happy with the current state.

In this case, it seems that nothing important would break by this change,
because IMHO there are very very little custom instances of
MonadWriter/Reader. I'm also a big fan of this proposal: http://hackage.
If it were implemented and standardized, we could refactor type classes
without breaking existing code (or breaking it just very little).
[In particular, it'd be possible to make Monad a subclass of Applicative,
something I've always regretted we don't have.]

Other than that, I'm not sure how adding "contained" goes against this
idea. If we split MonadWriter into two type classes, "contained" would
simply go into the one with "listen" and "pass".

Best regards,

> Also, the former can be done purely within the mtl, while the latter drags
> transformers into it.

> -Edward
> On Jan 27, 2013, at 3:30 PM, Petr P <petr.mvd at> wrote:
>   Dear maintainers,
> I have two suggestions for MonadWriter:
> (1) Remove the "Monoid w" constraint from the definition.
> The constraints prevent creating new instances of the class that have
> only an implied monoid. For example, I needed to create a simple writer
> which always stores the last written element. I had to wrap it into Last,
> which was a nuisance for users of my library. Without the constraint, my
> instance would be quite simpler and still satisfying all the laws. There
> are many other similar use cases, like counting the number of written
> values (and disregarding their actual content) etc.
> The constraint is meant to ensure that instances of that class obey the
> monad laws. But it's not the responsibility of a type class that its
> instances satisfy the laws. They could violate them even without this
> constraints. Instead, this constraint should be specified (and it is) in
> the definition of their instances.
> It has been discussed in haskell-cafe <
> haskell-cafe/2012-December/thread.html#105088> with arguments for and
> against.
> (2) Add
>   -- | @contained m@ executes the action @m@ in a contained environment
> and
>   -- returns its value and its output. The current output is not
> modified.
>   contained :: m a -> m (a, w)
> to MonadWriter.
> This generalizes "pass" and "listen"  and has it's a sort of inverse to
> "writer" with these simple laws:
>   writer <=< contained   = id
>   contained . writer     = return
> It seems as a understandable set of laws that its instances should obey.
> It also expresses the same concept as "runWriterT" does, but inside the
> type class. In particular, for "WriterT" we have
>   contained :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
>   contained = lift . runWriterT
> Current instances won't be affected as "contained" can be expressed using
> "pass" and "listen" (and vice versa).
> Full details available at
> [There "contained" is expressed without the "Monoid w" constraint as
> suggested in (1). If we keep the constraint, "contained" can be expressed
> more simply as
>   containde k = pass (listen k >>= \x -> return (x, const mempty)).
> Also, "contained" isn't probably a good name, I just couldn't think of
> anything better.]
>    Best regards,
>    Petr Pudlak
> _______________________________________________
> Libraries mailing list
> Libraries at
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the Libraries mailing list