I guess you have a point here:
1. The definition of the MonadWriter operations does not need the Monoid
operations.
Hence, the class constraint Monoid w should be removed.
2. The formulation of the MonadWriter laws (which are sadly missing from
the documentation) would need the Monoid operations, though, e.g.
tell v >> tell w = tell (v <> w)
This is a weak indication that a constraint Monoid w should be
present. However, currently Haskell does not provide a formal
specification of laws (except maybe as RULES!?), so having the
constraint here is a bit too eager. The story would be different in Agda...
Indeed, if one wants Roman's MyWriter to be an instance of MonadWriter,
one needs to declare an annoying fake monoid instance for Integer.
{-# LANGUAGE MultiParamTypeClasses, DeriveFunctor,
GeneralizedNewtypeDeriving #-}
import Control.Arrow
import Control.Applicative
import Control.Monad.Writer
import Data.Monoid
newtype MyWriter a = MyWriter { myWriter :: Writer (Sum Integer) a }
deriving (Functor, Monad)
instance Monoid Integer where
instance MonadWriter Integer MyWriter where
tell w = MyWriter $ tell $ Sum w
listen m = MyWriter $ (id *** getSum) <$> listen (myWriter m)
pass m = MyWriter $ pass $ (id *** \ f -> Sum . f . getSum) <$>
myWriter m
Petr, you might wanna make a propsal to remove the Monoid w constraint
to librar...@haskell.org, summarizing the current state of discussion so
far.
Cheers,
Andreas
On 09.12.2012 11:04, Petr P wrote:
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 ())
This seems a contrived way of getting 'empty'. In this case, I prefer
the monoid instance.
-- 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 <r...@ro-che.info <mailto:r...@ro-che.info>>
* Edward Z. Yang <ezy...@mit.edu <mailto:ezy...@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
--
Andreas Abel <>< Du bist der geliebte Mensch.
Theoretical Computer Science, University of Munich
Oettingenstr. 67, D-80538 Munich, GERMANY
andreas.a...@ifi.lmu.de
http://www2.tcs.ifi.lmu.de/~abel/
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe