修改变换器堆栈中的内部阅读器

时间:2017-09-06 17:01:14

标签: haskell monad-transformers reader-monad

我将来自不同地方的代码汇总在一起,我试图处理以下内容:

问题

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

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有关,但我对他们的工作并不熟悉。

1 个答案:

答案 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这样的层。这正是MFunctormmorph个实例自动执行的操作。