`MonadState` and functional dependencies

Hello, yesterday I was looking at Monadstate and wondering about the functional dependency in the definition, a dependency that does not allow you to have a MonadState instance that focuses on a part of your BigState™.

Yesterday I found myself writing something like this:

{-# Language GeneralizedNewtypeDeriving #-}
{-# Language FlexibleContexts #-}

import Control.Monad.State.Strict

newtype MyState = MyState { str :: String,
                            bl :: Bool }
testState = MyState "foo" True
newtype Scaffold a = Scaffold (State MyState a)
        deriving (Functor, Applicative, Monad, MonadState MyState)

This allows us to write a polymorphic function like

f :: (MonadState MyState m) => m Int
f = fromEnum <$> gets bl

prova :: Int
prova = evalState f testState
    -- this will of course work with any instance (MonadState Mystate)

Which is extremely handy when you do not want to tie your code to a concrete type. But say now I have another function similar function which operates on Bool rather than MyState as state:

-- I can write this
g :: (MonadState Bool m) => m Int
g = fromEnum <$> get

instance MonadState Bool Scaffold where
{- This errors with:
    src/Piece.hs:30:48-65: error:
    Functional dependencies conflict between instance declarations:
      instance MonadState MyState Scaffold
        -- Defined at src/Piece.hs:30:48
      instance MonadState Bool Scaffold -- Defined at src/Piece.hs:43:10 -}

I understand that the reason of functional dependencies is to avoid having to write countless annotations to make the compiler happy, but in this case I lose the ability of writing polymorphic code.

You can write a new class similar to

class MonadBool m where
    putMM :: Menu -> m ()

but then you lose some convenience operators and similar goodies.

I am sure I must not be the first to have met this, is there a better way to walk around or approach the problem?

I think I would just use gets bl:

g :: MonadState MyState m => m Int
g = fromEnum <$> gets bl

Or maybe something more complicated like this:

class HasBool a where
  getBool :: a -> Bool

g :: (HasBool s, MonadState s m) => m Int
g = fromEnum <$> gets getBool
1 Like

This works, but alas I believe in both gs I wuold be able to modify the «outer» state, am I wrong?
edit: I see, not in the second; there I cannot meaningfully change anything, as s is not concrete.

Yes, the first g has access to the concrete MyState record.

1 Like

The trick here is to “run” the smaller state computation and embed that into the larger state computation:

embed :: MonadState s m => (s -> s') -> (s' -> s) -> StateT s' m a -> m a
embed f g m = do
  s <- get
  (s', a) <- runStateT m (f s)
  put (g s')
  return a

See also zoom from lens, and mapStateT from transformers

2 Likes

From zoom docs in microlens-mtl:

Now, here’s an action that moves the player north-east:

moveNE :: State Game ()
moveNE = do
  player.position.x += 1
  player.position.y += 1

With zoom, you can use player.position to focus just on a part of the state:

moveNE :: State Game ()
moveNE = do
  zoom (player.position) $ do
    x += 1
    y += 1

That looks super useful, thanks for the tip off!

2 Likes