6

我正在从许多不同的地方收集代码,我正在尝试处理以下问题:

问题

我有一个具有以下简化类型的变压器堆栈:

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 是外部单子,而我想在内部单子上使用它。

我也认为这可能与 MonadBase 或 MonadTransControl 有关,但我不熟悉它们的工作原理。

4

1 回答 1

5

我认为不可能编写带有签名的函数:

changeReaderT :: (MonadTrans m)
                 => (r -> r') 
                 -> m (ReaderT r IO) a 
                 -> m (ReaderT r' IO) a

问题是,一般来说,第二个参数唯一可能的操作是将它提升到t (m (ReaderT r IO)) a一些 monad transformer t,这不会给你带来任何东西。

也就是说,MonadTrans m单独的约束并不能提供足够的结构来做你想做的事。您要么需要m像包中这样的类型类的实例,MFunctormmorph允许您通过提供如下函数以一般方式修改 monad 堆栈的内层:

hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b

(这就是@Juan Pablo Santos 所说的),否则您需要能够深入挖掘mmonad 转换器的结构以部分运行和重建它(这将是特定于转换器的)。

如果您已经由包支持的转换器组成,第一种方法(hoistmmorph包中使用)将是最方便的。例如,以下类型检查,您不必编写任何实例:mmmorph

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自动执行的操作。

于 2017-09-06T21:20:32.773 回答