修改变压器堆栈中的内部读取器

Oll*_*ieB 6 haskell monad-transformers reader-monad

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

问题

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

action :: m (ReaderT r IO) a
Run Code Online (Sandbox Code Playgroud)

我正在尝试在不同堆栈的上下文中使用该操作,该堆栈具有不同的读取器环境:

desired :: m (ReaderT r' IO) a
Run Code Online (Sandbox Code Playgroud)

我当然可以提供

f :: r' -> r
Run Code Online (Sandbox Code Playgroud)

things :: m (ReaderT r' IO) ()
things = do
   -- ... some stuff

   -- <want to use action here>
   action :: m (ReaderT r IO) a -- broken

    -- ... more stuff
   pure ()
Run Code Online (Sandbox Code Playgroud)

我考虑过的

withReaderT :: (r' -> r) -> ReaderT r m a -> ReaderT r' m a
Run Code Online (Sandbox Code Playgroud)

这有一个问题,ReaderT是外部monad,而我想在内部使用它.

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

K. *_*uhr 5

我不认为写一个带签名的函数是可能的:

changeReaderT :: (MonadTrans m)
                 => (r -> r') 
                 -> m (ReaderT r IO) a 
                 -> m (ReaderT r' IO) a
Run Code Online (Sandbox Code Playgroud)

问题在于,一般来说,唯一可能的操作就是第二个论点是将它提升到t (m (ReaderT r IO)) a一些monad变压器t,它不会给你任何东西.

也就是说,MonadTrans m仅限制约束并不能提供足够的结构来完成您想要的任务.你要么必须m要像一个类型类的实例MFunctormmorph,允许您通过提供功能类似修改一般的方式单子堆栈的内层包装:

hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
Run Code Online (Sandbox Code Playgroud)

(这就是@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
Run Code Online (Sandbox Code Playgroud)

你需要hoist为每一层提供一个M.

第二种方法避免了hoist必需的MFunctor实例,但需要根据您的具体情况进行定制M.对于上面的类型,它看起来像:

desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
  (withReaderT fromIntegral . flip runStateT s . runMaybeT) action
Run Code Online (Sandbox Code Playgroud)

你基本上需要将monad运行到ReaderT图层然后重新构建它StateT,小心处理图层.这正是MFunctor实例mmorph自动执行的操作.