考虑下一个示例。我有一个monad 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
  ...

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

谢谢。

最佳答案

听起来您想要的实质上是askget具有相同的效果。我忍不住想知道为什么在这种情况下您不只使用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值。我还没有彻底研究这在实践中的有用性,但是,要生成forall m. MonadReader m => m a类型的值,您几乎只能使用asklocal和monadic函数。

这是使用标准变压器的类似的,不太通用但可能更有用的东西:
readerToState :: (Monad m) => ReaderT r m a -> StateT r m a
readerToState reader = StateT $ \env -> do
  res <- runReaderT reader env
  return (res, env)

编辑:稍后考虑一下,您可能可以使用lift . runReaderT reader =<< get将其推广到任何状态的monad转换器,但是类型开始变得笨拙:
:: (Monad m, MonadTrans t, MonadState (t m)) => ReaderT (StateType (t m)) m b -> t m b

这是上面的概括,但实际上可能不是有用的。

09-04 10:58