我将来自不同地方的代码汇总在一起,我试图处理以下内容:
我有一个变压器堆栈,其简化类型如下:
action :: m (ReaderT r IO) a
我尝试在不同堆栈的上下文中使用该操作,该堆栈具有不同的读取器环境:
desired :: m (ReaderT r' IO) a
我当然可以提供
f :: r' -> r
things :: m (ReaderT r' IO) ()
things = do
-- ... some stuff
-- <want to use action here>
action :: m (ReaderT r IO) a -- broken
-- ... more stuff
pure ()
withReaderT :: (r' -> r) -> ReaderT r m a -> ReaderT r' m a
这有一个问题,ReaderT是外部monad,而我想在内部使用它。
我也认为这可能与MonadBase或MonadTransControl有关,但我对他们的工作并不熟悉。
答案 0 :(得分:5)
我认为编写带签名的函数是不可能的:
changeReaderT :: (MonadTrans m)
=> (r -> r')
-> m (ReaderT r IO) a
-> m (ReaderT r' IO) a
问题在于,对于第二个参数,唯一可能的操作是将其提升到t (m (ReaderT r IO)) a
以获得某些monad变换器t
,它不会给你任何东西。
也就是说,MonadTrans m
约束本身并没有提供足够的结构来做你想要的事情。您需要m
作为MFunctor
包中类似mmorph
的类型类的实例,它允许您通过提供类似函数来以一般方式修改monad堆栈的内层:
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
(这就是@Juan Pablo Santos所说的),否则你需要有能力深入研究m
monad变换器的结构,以部分运行和重建它(这将是变压器特定的)。
如果您的hoist
已由mmorph
包支持的变换器组成,则第一种方法(使用m
包中的mmorph
)将非常方便。例如,以下类型检查,您不必编写任何实例:
type M n = MaybeT (StateT String n)
action :: M (ReaderT Double IO) a
action = undefined
f :: Int -> Double
f = fromIntegral
desired :: M (ReaderT Int IO) a
desired = (hoist $ hoist $ withReaderT fromIntegral) action
hoist
中每个图层都需要M
。
第二种方法可以避免hoist
和必需MFunctor
个实例,但需要针对您的特定M
进行定制。对于上面的类型,它看起来像:
desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
(withReaderT fromIntegral . flip runStateT s . runMaybeT) action
你基本上需要将monad运行到ReaderT
层,然后重新构建它,小心处理像StateT
这样的层。这正是MFunctor
中mmorph
个实例自动执行的操作。