GHC 8.0.1 errors from Writer and State monads


I'm relatively new to Haskell and trying to port the UNIX wc program demonstrated in Kernighan and Ritchie using monadic composition, as shown in The Essence of the Iterator Pattern by Jeremy Gibbons and Bruno C. d. S. Oliveira, and having some trouble getting it to compile. Here's my code:

import Control.Monad.Writer
import Control.Monad.State
import Data.Char

test :: Bool -> Integer
test b = if b then 1 else 0

ccmBody :: Char -> Writer Integer Char
ccmBody c = do
  tell 1
  return c

ccm :: String -> Writer Integer String
ccm = mapM ccmBody

lcmBody :: Char -> Writer Integer Char
lcmBody c = do
  tell(test(c == '\n'))
  return c

lcm' :: String -> Writer Integer String
lcm' = mapM lcmBody

wcmBody :: Char -> State (Integer, Bool) Char
wcmBody c = let s = not (isSpace c) in do
              (n,w) <- get
              put (n + test(not (w || s)), s)
              return c

wcm :: String -> State (Integer, Bool) String
wcm = mapM wcmBody

clwcm = ccm >=> lcm' >=> wcm

And the compiler errors:

wordcount.hs:10:3: error: …
    • No instance for (Monoid Integer) arising from a do statement
    • In a stmt of a 'do' block: tell 1
       In the expression:
        do { tell 1;
             return c }
      In an equation for ‘ccmBody’:
          ccmBody c
            = do { tell 1;
                   return c }
wordcount.hs:33:26: error: …
    • Couldn't match type ‘StateT
                             (Integer, Bool) Data.Functor.Identity.Identity’
                     with ‘WriterT Integer Data.Functor.Identity.Identity’
      Expected type: String
                     -> WriterT Integer Data.Functor.Identity.Identity String
        Actual type: String -> State (Integer, Bool) String
    • In the second argument of ‘(>=>)’, namely ‘wcm’
      In the second argument of ‘(>=>)’, namely ‘lcm' >=> wcm’
      In the expression: ccm >=> lcm' >=> wcm
Compilation failed.

I can't understand what I'm doing wrong here in either case. Any help would be greatly appreciated. Thanks!

Show source
| haskell   | monads   2016-12-10 11:12 1 Answers

Answers ( 1 )

  1. 2016-12-10 14:12

    For the Writer monad to work, the object you're writing has to be an instance of Monoid. tell uses mappend to add its argument to the running log.

    For integers, however, there are at least two good choices for a Monoid instance. One of them is addition, with 0 as the unit element:

    instance Monoid Int where
        mempty = 0
        mappend = (+)

    and the other is multiplication with 1:

    instance Monoid Int where
        mempty = 1
        mappend = (*)

    How should the Haskell designers choose between these two options? They're both equally valid, and both useful in different situations. In the end they decided not to express an opinion, leaving Int without a Monoid instance of its own and instead providing two newtypes to allow you to choose which instance you want.

    newtype Sum n = Sum { getSum :: n }
    instance Num n => Monoid (Sum n) where
        mempty = Sum 0
        Sum x `mappend` Sum y = Sum (x + y)
    newtype Product n = Product { getProduct :: n }
    instance Num n => Monoid (Product n) where
        mempty = Product 1
        Product x `mappend` Product y = Product (x * y)

    These two newtypes are found in the Data.Monoid module. I'm guessing in your case you want to add up the numbers in the Writer monad, so all you should need to do is change all your type signatures from Writer Integer to Writer (Sum Integer).

    The other type error is GHC telling you that you can't compose a Writer action with a State action. You have wcm :: State (Integer, Bool) String and ccm :: Writer Integer String. Skim-reading your code, it looks like you're only using the Integer component of your state to add things up (I'm guessing it's meant to take part in the running total along with the Writer bits?), so I would consider using the monad transformer version of State:

    wcm :: StateT Bool (Writer Integer) String

    and then using lift to bring the plain old Writer Integer monads into the StateT-enriched context.

    If you're not used to monad transformers yet, the other option is to write everything in the State (Integer, Bool) monad.

◀ Go back