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
Mem
is 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
Mem
with 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,
mempty
andmappend
We want to define a Monoid instance so we have a reliable way of associating two
Mem
values with one another.To do that, we need to define
mempty
as the identity ofMem
, just as0
is the identity of addition.We also need to define
mappend
so that the act of associating twoMem
values will produce a newMem
value:myFirstMem <> mySecondMem