3

考虑下一个例子。我有一个单子MyM,它只是一个StateT

{-# LANGUAGE TypeFamilies #-}

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

type MyS = Int
type MyM = StateT MyS

通常MyM用于读写MyS状态,所以我有如下功能:

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

但有时我只需要阅读MyS,所以我想要MonadReader上下文而不是MonadState

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

我想写一些类似的东西:

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

所以基本上我需要每个MonadState实例都是MonadReader对应的家庭类型的实例。就像是

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

但我找不到如何进行类型检查的方法。MonadState是否可以表达和之间的这种关系MonadReader

谢谢。

4

1 回答 1

4

听起来您想要的本质ask上与get. 我不禁想知道为什么你不只是get在那种情况下使用:)

如果您的目标是编写代码,根据可用的内容读取状态或环境,您必须询问您打算做什么,例如,ReaderT r (StateT s m) a您在哪里拥有这两者。出于这个原因,你不能这样做:

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

因为您会与现有实例发生冲突。但是,您可以这样做:

{-# 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

然后,如果您有一个多态读取器值f2,您可以MonadState使用unRS. 如果你想使用一些更狡猾的扩展,试试这个RankNTypes

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

然后你可以做useStateAsEnv (liftM (+1) ask)并获得一个MonadState价值。然而,我还没有彻底研究过这在实践中有多有用——要产生一个 type 的值forall m. MonadReader m => m a,你几乎只能使用ask,local和 monadic 函数。

这是一个类似的,不太通用但可能更有用的东西,使用标准转换器:

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

编辑:稍后考虑这一点,您可能可以将其推广到任何带有 的状态 monad 转换lift . runReaderT reader =<< get器,但类型开始变得相当笨拙:

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

对上述内容的概括,但实际上可能不是有用的。

于 2011-09-03T12:52:26.363 回答