This gist and its comments are a way of working through an exercise problem found in Chapter 15 of the Haskell Book.
Here's the code from the exercise. Our job is to implement the mempty and mappend functions, which are presented here as undefined:
module Chap15Ex where
import Data.Monoid
newtype Mem s a =
Mem {
runMem :: s -> (a, s)
}
instance Monoid a => Monoid (Mem s a) where
mempty = undefined
mappend = undefined
f' :: Mem Integer String
f' = Mem $ \s -> ("hi", s + 1)
main = do
let rmzero = runMem mempty 0
rmleft = runMem (f' <> mempty) 0
rmright = runMem (mempty <> f') 0
print $ rmleft -- ("hi, 1)
print $ rmright -- ("hi", 1)
print $ (rmzero :: (String, Int)) -- ("", 0)
print $ rmleft == runMem f' 0 -- True
print $ rmright == runMem f' 0 -- True
Here's what we know so far:
newtypes
The newtype
Memis a product type with special record syntax. It's a type that contains one value, a function with the signatures -> (a, s).This syntax is equivalent to defining
Memwith a data constructor that accepts a function, along with a helper function to pull the value out:The State monad
The function signature
s -> (a, s)is a state monad. It's used to pass state around and update it when need be. I liked this example from Learn You a Haskell... that implemented a basic stack that you could pop and push numbers onto:monoids,
memptyandmappendWe want to define a Monoid instance so we have a reliable way of associating two
Memvalues with one another.To do that, we need to define
memptyas the identity ofMem, just as0is the identity of addition.We also need to define
mappendso that the act of associating twoMemvalues will produce a newMemvalue:myFirstMem <> mySecondMem