1

in Haskell, I would like to make a Writer monad an instance of a monoid:

instance (Monoid a) => Monoid (Writer (Sum Int) a) where
  mempty = return mempty
  w1 `mappend` w2 = writer((s++t, s'++t'), Sum (m+n)) where
    ((s,s'), Sum m) = runWriter w1
    ((t,t'), Sum n) = runWriter w2

So, intuitively, if the "data" type of the Writer monad is a monoid, I want to be able to consider the whole Writer thing as a monoid as well (as implemented by mempty and mappend.

This doesn't work, though: The GHCI compiler says

Illegal instance declaration for `Monoid (Writer (Sum Int) a)'
  (All instance types must be of the form (T t1 ... tn)
   where T is not a synonym.
   Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Monoid (Writer (Sum Int) a)'

and I really don't know what type is supposed to be a synonym here and how I can conform to the compiler's rules.

Philipp Wacker
  • 253
  • 2
  • 7
  • 1
    Do not conform to the rules: relax them by enabling `-XTypeSynonymInstances` as the compiler suggests. `-XFlexiblesInstances` may also be required. – user2407038 Jul 31 '16 at 22:57

2 Answers2

3

Everybody is doing so much work. The bind operator on a writer monad already appends the ws. This also means it will work for an arbitrary base monad.

instance (Monoid w, Monoid a, Monad m) => Monoid (WriterT w m a) where
    mempty = return mempty
    mappend = liftA2 mappend

At this point it's clear that even WriterT is superfluous, and this is actually an "instance" of this general instance

instance (Monoid a, Monad m) => Monoid (m a) where
    -- same

but Haskell's class system doesn't really permit instances like this -- it would match every monoid built from a type constructor. For example this instance would match Sum Int, and then fail because Sum isn't a monad. So you have to specifify it seperately for each monad you're interested in.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • 1
    Surely there's an extension to do that, isn't there? – Bergi Aug 01 '16 at 01:42
  • 1
    How embarrassing! After all, I'm the one who proposed adding `Ap` to `Data.Monoid`, with `newtype Ap f a = Ap (f a)`, `instance (Applicative f, Monoid a) => Monoid (Ap f a)`. Note that `Monad` is overkill. – dfeuer Aug 01 '16 at 21:30
  • @Bergi, there are overlapping instances, but they're evil. – dfeuer Aug 01 '16 at 21:31
2

Writer is a type alias (link)

type Writer w = WriterT w Identity

so use WriterT ... Identity instead. You'll still need to enable FlexibleInstances.

Perhaps this is what you are after:

{-# LANGUAGE FlexibleInstances #-}

import Control.Monad.Trans.Writer
import Data.Monoid
import Data.Functor.Identity

instance (Monoid w, Monoid a) => Monoid (WriterT w Identity a) where
  mempty = return mempty
  m1 `mappend` m2 = writer (a1 <> a2, w1 <> w2)
    where
      (a1,w1) = runWriter m1
      (a2,w2) = runWriter m2

Of course, this could be generalized to an arbitrary Monad instead of Identity.

ErikR
  • 51,541
  • 9
  • 73
  • 124