[Haskell-cafe] mtl: Why there is "Monoid w" constraint in the definition of class MonadWriter?

Petr P petr.mvd at gmail.com
Sun Dec 9 11:04:18 CET 2012


  Hi all,

I'd say that a type class declares functions and specifies laws (in the
docs)
what its implementations must adhere to. It's not the job of a type class to
fulfill the laws, it's the job of its implementations. So the reason for
'Monoid w' in 'MonadWriter' cannot be that then 'MonadWriter' wouldn't be a
monad. Such constraints should be required only by implementations.

It is true that any Writer has an implicit underlying monoid, and we can
even "extract" the operations from it as follows. The empty element can be
extracted as

     empty = liftM snd (listen (return ())) :: m w

Having this 'empty', we can give 'const empty' to 'pass' to discard output
of
an action, so we can construct:

    -- | @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)
    contained k = do
        -- we can retrieve mempty even if we don't have the monoid
constraint:
        ~(_, empty) <- listen (return ())
        -- listen what @k@ does, get its result and ignore its output
change:
        pass (listen k >>= \x -> return (x, const empty))

This generalizes 'listen' and 'pass' (both can be easily defined from it)
and I find this function much easier to understand. In a way, it is also a
generalization of WriterT's runWriterT, because for WriterT we have
'contained = lift . runWriterT'.

[I implemented 'contained' in a fork of the mtl library, if anybody is
interested: https://github.com/ppetr/mtl ]

With that, we can do

    -- Doesn't produce any output, only returns the combination
    -- of the arguments.
    append x y = liftM snd $ contained (tell x >> tell y) :: w -> w -> m w

I didn't check the monoid laws, but it seems obvious that they follow from
the
monad laws and (a bit vague) specification of 'listen' and 'pass'.

Personally, I'd find it better if `MonadWriter` would be split into two
levels:
One with just 'tell' and 'writer' and the next level extending it with
'listen'/'pass'/'contained'. The first level would allow things like
logging to
a file, without any monoidal structure. But this would break a lot of stuff
(until we agree on and develop something like
http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances).

    Best regards,
    Petr



2012/12/9 Roman Cheplyaka <roma at ro-che.info>

> * Edward Z. Yang <ezyang at MIT.EDU> [2012-12-08 15:45:54-0800]
> > > Second, even *if* the above holds (two tells are equivalent to one
> > > tell), then there is *some* function f such that
> > >
> > >     tell w1 >> tell w2 == tell (f w1 w2)
> > >
> > > It isn't necessary that f coincides with mappend, or even that the type
> > > w is declared as a Monoid at all. The only thing we can tell from the
> > > Monad laws is that that function f should be associative.
> >
> > Well, the function is associative: that's half of the way there to
> > a monoid; all you need is the identity!  But we have those too:
> > whatever the value of the execWriter (return ()) is...
>
> Let me repeat:
>
>   It isn't necessary that f coincides with mappend, or even that the
>   type w is declared as a Monoid at all.
>
> Let me illustrate this with an example.
>
>   data MyWriter a = MyWriter Integer a
>
>   instance Monad MyWriter where
>     return = MyWriter 0
>     MyWriter n x >>= k =
>       let MyWriter n' y = k x
>       in MyWriter (n+n') y
>
>   instance MonadWriter Integer MyWriter where
>     tell n = MyWriter n ()
>     listen (MyWriter n x) = return (x,n)
>     pass (MyWriter n (a,f)) = MyWriter (f n) a
>
> Yes, integers do form a monoid when equipped with 0 and (+). However, we
> know well why they are not an instance of Monoid — namely, there's more
> than one way they form a monoid.
>
> Even if something is in theory a monoid, there may be technical reasons
> not to declare it a Monoid. Likewise, imposing a (technical) superclass
> constraint on MonadWriter has nothing to do with whether the Monad will
> be well-behaved.
>
> This is true in both directions: even if the type is an instance of
> Monoid, nothing forces the Monad instance to use the Monoid instance.
> I.e. I can declare a MonadWriter on the Sum newtype whose bind, instead
> of adding, subtracts the numbers.
>
> Roman
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20121209/ad57c541/attachment.htm>


More information about the Haskell-Cafe mailing list