Hi all,<div><br></div><div><div>I'd say that a type class declares functions and specifies laws (in the docs)</div><div>what its implementations must adhere to. It's not the job of a type class to</div><div>fulfill the laws, it's the job of its implementations. So the reason for</div>
<div>'Monoid w' in 'MonadWriter' cannot be that then 'MonadWriter' wouldn't be a</div><div>monad. Such constraints should be required only by implementations.</div><div><br></div><div>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</div>
<div><br></div><div> empty = liftM snd (listen (return ())) :: m w</div><div><br></div><div>Having this 'empty', we can give 'const empty' to 'pass' to discard output of</div><div>an action, so we can construct:</div>
<div><br></div><div> -- | @contained m@ executes the action @m@ in a contained environment and</div><div> -- returns its value and its output. The current output is not modified.</div><div> contained :: m a -> m (a, w)</div>
<div> contained k = do</div><div> -- we can retrieve mempty even if we don't have the monoid constraint:</div><div> ~(_, empty) <- listen (return ())</div><div> -- listen what @k@ does, get its result and ignore its output change:</div>
<div> pass (listen k >>= \x -> return (x, const empty))</div><div><br></div><div>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</div>
<div>'contained = lift . runWriterT'.</div><div><br></div><div>[I implemented 'contained' in a fork of the mtl library, if anybody is</div><div>interested: <a href="https://github.com/ppetr/mtl">https://github.com/ppetr/mtl</a> ]</div>
<div><br></div><div>With that, we can do</div><div><br></div><div> -- Doesn't produce any output, only returns the combination</div><div> -- of the arguments.</div><div> append x y = liftM snd $ contained (tell x >> tell y) :: w -> w -> m w</div>
<div><br></div><div>I didn't check the monoid laws, but it seems obvious that they follow from the</div><div>monad laws and (a bit vague) specification of 'listen' and 'pass'.</div><div><br></div><div>
Personally, I'd find it better if `MonadWriter` would be split into two levels:</div><div>One with just 'tell' and 'writer' and the next level extending it with</div><div>'listen'/'pass'/'contained'. The first level would allow things like logging to</div>
<div>a file, without any monoidal structure. But this would break a lot of stuff</div><div>(until we agree on and develop something like <a href="http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances">http://hackage.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances</a>).</div>
<div><br></div><div> Best regards,</div><div> Petr</div></div><div><br></div><div class="gmail_extra"><br><br><div class="gmail_quote">2012/12/9 Roman Cheplyaka <span dir="ltr"><<a href="mailto:roma@ro-che.info" target="_blank">roma@ro-che.info</a>></span><br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">* Edward Z. Yang <<a href="mailto:ezyang@MIT.EDU">ezyang@MIT.EDU</a>> [2012-12-08 15:45:54-0800]<br>
<div class="im">> > Second, even *if* the above holds (two tells are equivalent to one<br>
> > tell), then there is *some* function f such that<br>
> ><br>
> > tell w1 >> tell w2 == tell (f w1 w2)<br>
> ><br>
> > It isn't necessary that f coincides with mappend, or even that the type<br>
> > w is declared as a Monoid at all. The only thing we can tell from the<br>
> > Monad laws is that that function f should be associative.<br>
><br>
> Well, the function is associative: that's half of the way there to<br>
> a monoid; all you need is the identity! But we have those too:<br>
> whatever the value of the execWriter (return ()) is...<br>
<br>
</div>Let me repeat:<br>
<div class="im"><br>
It isn't necessary that f coincides with mappend, or even that the<br>
type w is declared as a Monoid at all.<br>
<br>
</div>Let me illustrate this with an example.<br>
<br>
data MyWriter a = MyWriter Integer a<br>
<br>
instance Monad MyWriter where<br>
return = MyWriter 0<br>
MyWriter n x >>= k =<br>
let MyWriter n' y = k x<br>
in MyWriter (n+n') y<br>
<br>
instance MonadWriter Integer MyWriter where<br>
tell n = MyWriter n ()<br>
listen (MyWriter n x) = return (x,n)<br>
pass (MyWriter n (a,f)) = MyWriter (f n) a<br>
<br>
Yes, integers do form a monoid when equipped with 0 and (+). However, we<br>
know well why they are not an instance of Monoid — namely, there's more<br>
than one way they form a monoid.<br>
<br>
Even if something is in theory a monoid, there may be technical reasons<br>
not to declare it a Monoid. Likewise, imposing a (technical) superclass<br>
constraint on MonadWriter has nothing to do with whether the Monad will<br>
be well-behaved.<br>
<br>
This is true in both directions: even if the type is an instance of<br>
Monoid, nothing forces the Monad instance to use the Monoid instance.<br>
I.e. I can declare a MonadWriter on the Sum newtype whose bind, instead<br>
of adding, subtracts the numbers.<br>
<span class="HOEnZb"><font color="#888888"><br>
Roman<br>
</font></span></blockquote></div><br></div>