开发者

monads-tf: MonadReader instance for MonadState

开发者 https://www.devze.com 2023-04-02 20:01 出处:网络
Consider the next example. I have a monad MyM that is just a StateT {-# LANGUAGE TypeFamilies #-} import Control.Monad.State

Consider the next example. I have a monad MyM that is just a StateT

{-# LANGUAGE TypeFamilies #-}

import Control.Monad.State
import Control.Monad.Reader

type MyS = Int
type MyM = StateT MyS

Usually MyM is used for reading and writing MyS state, so I have functions like the next:

f1 :: (MonadState m, StateType m ~ MyS) => m ()
f1 = modify (+1)

But sometimes I need just to read MyS, so I want MonadReader context instead of MonadState:

f2 :: (MonadReader m, EnvType m ~ MyS) => m Int
f2 = liftM (+1) ask

And I want to write something like:

f3 :: (MonadState m, StateType m ~ MyS) => m Int
f3 = f1 >> f2

So basically I need every MonadState instance to be MonadReader instance too with the correspondent family type. Something开发者_Python百科 like

instance MonadState m => MonadReader where
  type EnvType m = StateType m
  ...

But I can't find the way how to make it type check. Is it possible to express such the relation between MonadState and MonadReader?

Thanks.


It sounds like what you want is essentially ask to have the same effect as get. I can't help but wonder why you don't just use get in that case :)

If your aim is to write code that either reads the state or the env depending on what is available, you have to ask what you plan to do with, say, ReaderT r (StateT s m) a, where you have both. For that reason, you can't just do:

instance MonadState m => MonadReader m where
  type EnvType m = StateType m
  ask = get

because you'll conflict with existing instances. You can, however, do:

{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies #-}
newtype ReadState m a = RS { unRS :: m a }
  deriving (Monad)

instance MonadState m => MonadReader (ReadState m) where
  type EnvType (ReadState m) = StateType m
  ask = RS get
  local f (RS m) = RS $ do
    s <- get
    modify f
    x <- m
    put s
    return x

Then if you have a polymorphic reader value like f2, you can pull a MonadState out of it with unRS. If you want to use some more devious extensions, try this with RankNTypes:

useStateAsEnv :: (MonadState n) => (forall m . (MonadReader m, EnvType m ~ StateType n) => m a) -> n a
useStateAsEnv m = unRS m

Then you can do useStateAsEnv (liftM (+1) ask) and get a MonadState value. I haven't thoroughly investigated how useful this is in practice, however – to produce a value of type forall m. MonadReader m => m a, you can pretty much only use ask, local, and monadic functions.

Here's a similar, less general but probably more useful thing, using standard transformers:

readerToState :: (Monad m) => ReaderT r m a -> StateT r m a
readerToState reader = StateT $ \env -> do
  res <- runReaderT reader env
  return (res, env)

Edit: thinking about this later you could probably generalise this to any state monad transformer with lift . runReaderT reader =<< get, but the type starts to be rather unwieldy:

:: (Monad m, MonadTrans t, MonadState (t m)) => ReaderT (StateType (t m)) m b -> t m b

which is a generalisation of the above but may not actually be a useful one.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号