修改转换器堆栈中的内部 reader
Modifying inner reader in a transformer stack
我正在从许多不同的地方收集代码,我正在尝试处理以下问题:
问题
我有一个具有以下简化类型的变压器组:
action :: m (ReaderT r IO) a
并且我正在尝试在具有不同 reader 环境的不同堆栈的上下文中使用该操作:
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,而我想在内部 monad 上使用它。
我也考虑过这可能与 MonadBase 或 MonadTransControl 有关,但我不熟悉它们的工作原理。
我不认为可以编写带有签名的函数:
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
成为 mmorph
包中 MFunctor
类的实例,它允许您通过提供类似的函数以一般方式修改 monad 堆栈的内层:
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
(这是@Juan Pablo Santos 所说的),否则您需要能够深入研究 m
monad 转换器的结构以部分 运行 并重建它(这将特定于变压器)。
如果您的 m
已经由 mmorph
包支持的转换器组成,第一种方法(使用 mmorph
包中的 hoist
)将是最方便的.例如,以下类型检查,您不必编写任何实例:
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
M
中的每一层都需要一个 hoist
。
第二种方法避免了 hoist
和必需的 MFunctor
实例,但需要根据您的特定 M
进行调整。对于上面的类型,它看起来像:
desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
(withReaderT fromIntegral . flip runStateT s . runMaybeT) action
你基本上需要 运行 monad 到 ReaderT
层,然后重建它,小心处理像 StateT
这样的层。这正是 mmorph
中的 MFunctor
个实例自动执行的操作。
我正在从许多不同的地方收集代码,我正在尝试处理以下问题:
问题
我有一个具有以下简化类型的变压器组:
action :: m (ReaderT r IO) a
并且我正在尝试在具有不同 reader 环境的不同堆栈的上下文中使用该操作:
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,而我想在内部 monad 上使用它。
我也考虑过这可能与 MonadBase 或 MonadTransControl 有关,但我不熟悉它们的工作原理。
我不认为可以编写带有签名的函数:
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
成为 mmorph
包中 MFunctor
类的实例,它允许您通过提供类似的函数以一般方式修改 monad 堆栈的内层:
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
(这是@Juan Pablo Santos 所说的),否则您需要能够深入研究 m
monad 转换器的结构以部分 运行 并重建它(这将特定于变压器)。
如果您的 m
已经由 mmorph
包支持的转换器组成,第一种方法(使用 mmorph
包中的 hoist
)将是最方便的.例如,以下类型检查,您不必编写任何实例:
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
M
中的每一层都需要一个 hoist
。
第二种方法避免了 hoist
和必需的 MFunctor
实例,但需要根据您的特定 M
进行调整。对于上面的类型,它看起来像:
desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
(withReaderT fromIntegral . flip runStateT s . runMaybeT) action
你基本上需要 运行 monad 到 ReaderT
层,然后重建它,小心处理像 StateT
这样的层。这正是 mmorph
中的 MFunctor
个实例自动执行的操作。