2

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.

bwroga
  • 5,379
  • 2
  • 23
  • 25
krokodil
  • 1,326
  • 10
  • 18
  • Why don't you want to unwrap the monad? – luqui Apr 19 '17 at 02:07
  • Ideally, I would like monad to track whole computation tree. Later I can use different Monoid to build a trace or do some other analysis. Implicitly unwrapping it will break this (I think). Full disclosure: the example above is in Haskell but I am actually implementing this in Coq :) – krokodil Apr 19 '17 at 02:16
  • 1
    However you end up solving this, you will need a way to combine the `XFlags` coming from `xplus` with the `SFlags` coming from `splus`. – Cactus Apr 19 '17 at 03:45
  • 1
    @krokodil, maybe you misunderstand monads. There's nothing you can do with a monad that you can't do without one -- it's just shorthand. So unwrap, do what you need to do, and wrap it back up again. If your monad is keeping track of computation trees, just don't throw away that info when you unwrap/wrap. – luqui Apr 19 '17 at 05:49
  • Just use the state monad? – Benjamin Hodgson Apr 19 '17 at 10:26
  • @BenjaminHodgson Writer monad guarantees that my computation results never depend on state. – krokodil Apr 19 '17 at 18:09
  • To reinforce @luqui 's point, if you check the [source of the `mapWriter` function](http://hackage.haskell.org/package/transformers-0.5.4.0/docs/src/Control.Monad.Trans.Writer.Lazy.html#mapWriter) that is used in [David Fletcher's answer](http://stackoverflow.com/a/43498169/2751851) you'll find that all it does is unwrapping, applying the function and wrapping it back again. – duplode Apr 19 '17 at 21:30

1 Answers1

2

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.)

David Fletcher
  • 2,590
  • 1
  • 12
  • 14