Search code examples
haskellmonadsmonad-transformerswriter-monad

Swapping `mappend` in Writer monad


Summary: While using Writer monad, I would like to be able to switch between 2 different versions of mappend without losing the state.

I use two boolean flags to track some state:

data Flags = F Bool Bool

Now I define two Monoid instances which differ in a way they combine the flags in mappend:

newtype XFlags = XF Flags

instance Monoid XFlags where
  mempty = XF (F True False)
  (XF (F s0 c0)) `mappend` (XF (F s1 c1)) = XF (F (s0 && s1)
  (c0 || c1 || not (s0 || s1)))

newtype SFlags = SF Flags

instance Monoid SFlags where
  mempty = SF (F True False)
  (SF (F s0 c0)) `mappend` (SF (F s1 c1)) = SF (F (s0 && s1) (c0 || c1))

Now I can have 2 Writer monads with different flags handling:

type XInt = WriterT XFlags Identity Int
type SInt = WriterT SFlags Identity Int

Now I can have operations like:

xplus :: XInt -> XInt -> XInt
xplus = liftM2 (+)

splus :: SInt -> SInt -> SInt
splus = liftM2 (+)

Now to I would like to build expressions like:

foo = splus (return 1) (xplus (return 2) (return 3))

To do so I need to be able to convert between the two without losing any flags and preferably without unwrapping the monad (using runWriter). This part I have not been figure out. It looks little bit like I can try to nest Writers using monad transformer but I am not sure if it is applicable directly here. I will appreciate some guidance on the best way to implement something like this.


Solution

  • You can get something reasonable using mapWriter.

    sFromX :: XInt -> SInt
    sFromX = mapWriter (\(x, XF fs) -> (x, SF fs))
    

    Now you can write foo like

    foo :: SInt
    foo = splus (return 1) (sFromX (xplus (return 2) (return 3)))
    

    You might need the opposite, xFromS as well. If you had more than two different monoids maybe it would be worth getting fancier and writing a class for flag containers, something like:

    class FlagContainer a where
      getFlags :: a -> Flags
      makeFromFlags :: Flags -> a
    

    Then use it to write a single function which would replace sFromX, xFromS, and any others you need. (Haven't tested this though.)