Search code examples
haskellmonadsstate-monad

How do you `get` the current state from a a state monad that is part of a product monad?


I am building some product monads from Control.Monad.Product package. For reference, the product monad type is:

newtype Product g h a = Product { runProduct :: (g a, h a) } 

Its monad instance is:

instance (Monad g, Monad h) => Monad (Product g h) where
    return a = Product (return a, return a)
    Product (g, h) >>= k = Product (g >>= fst . runProduct . k, h >>= snd . runProduct . k)
    Product (ga, ha) >> Product (gb, hb) = Product (ga >> gb, ha >> hb)

source: http://hackage.haskell.org/packages/archive/monad-products/3.0.1/doc/html/src/Control-Monad-Product.html

Problem I

I build a simple monad that is a product of two State Int Monads, However, when I try to access the underlying state next:

ss :: Product (State Int) (State Int) Int
ss = do 
  let (a,b) = unp $ P.Product (get,get) :: (State Int Int,State Int Int)  
  return 404

You see get just creates another State Int Int, and I am not sure how to actually get the value of the underlying state, how might I do that? Note I could potentially runState a and b to get the underlying value, but this solution doesn't seem very useful since the two states' initial values must be fixed a priori.

Question II.

I would really like to be able to create a product monad over states of different types, ie:

ss2 :: Product (State Int) (State String) ()
ss2 = do 
  let (a,b) = unp $ P.Product (get,get) :: (State Int Int,State Int String)  
  return ()

But I get this type error:

 Couldn't match expected type `String' with actual type `Int'
    Expected type: (State Int Int, State String String)
      Actual type: (StateT Int Identity Int,
                    StateT String Identity Int)

Because I presume the two get must return the same type, which is an unfortunate restriction. Any thoughts on how to get around this?


Solution

  • The solution is to use a state monad with a product of your states:

    m :: State (Int, String) ()
    

    Then you can run an operation that interacts with one of the two fields of the product using zoom and _1/_2 from the lens library, like this:

    m = do
        n <- zoom _1 get
        zoom _2 $ put (show n)
    

    To learn more about this technique, you can read my blog post on lenses which goes into much more detail.