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.
精彩评论