5

问题是这样的。我有:

f :: MonadIO m => ReaderT FooBar m Answer;
f = (liftIO getArgs) >>= ...

我需要使用修改后的参数来运行它。但是,由于 m 是未知的,我不能简单地使用

mapReaderT (withArgs args) :: ReaderT r IO b -> ReaderT r IO b

因为我需要以某种方式将 (withArgs args) 转换为所有 m 的 m。

我发现的一种可能性是定义我自己的 withArgs,因此:

import System.Environment (setArgs, freeArgv);
withArgv new_args act = do {
  pName <- liftIO System.Environment.getProgName;
  existing_args <- liftIO System.Environment.getArgs;
  bracket (liftIO $ setArgs new_args)
          (\argv -> do {
                      _ <- liftIO $ setArgs (pName:existing_args);
                      liftIO $ freeArgv argv;
                    })
          (const act);
};

withArgs xs act = do {
  p <- liftIO System.Environment.getProgName;
  withArgv (p:xs) act;
};

但是,这是一个杂项,并且特定于一个函数——我需要重新编写每个函数withX :: X -> IO a -> IO a,例如 Control.Exception.handle

如果有的话,有什么更好的方法来做到这一点?

编辑:在句柄的情况下,我找到了 Control.Monad.CatchIO。在另一种情况下,我使用了另一个更简短的拼凑(不值得发布)来避免上面的拼凑。仍在寻求更好的解决方案!

4

4 回答 4

9

您正在寻找的部分内容是将单子同态提升到单子变压器中。

class MonadHoist t where
    hoist :: (Monad m, Monad n) => (forall a. m a -> n a) -> t m a -> t n a

    t :: Monad m => t Identity a -> t m a
    t = hoist (return . runIdentity)

也就是说,给定一个fmto的单子同态n,你可以使用葫芦得到一个从t mto的单子同态。t n

monad 同态比上面强制执行的类型稍强,即它负责保持 monad 法则。

f . return = return
f . fmap g = fmap g . f
f . join = join . f . fmap f
         = join . fmap f . f -- by the second law
         = (>>= f) . f       -- >>= in terms of join

请注意我偷偷添加到 类型 中的量词hoistMonadHoist结果证明几乎所有实例都需要这种灵活性!(Reader碰巧是它没有的一种情况。尝试在没有它的情况下编写 MaybeT 。)

通常,Monad 转换器可以实例化此类。例如:

instance MonadHoist (StateT s) where
    hoist f (StateT m) = StateT (f . m)

instance MonadHoist (ReaderT e) where
    hoist f (ReaderT m) = ReaderT (f . m)

instance MonadHoist MaybeT where
    hoist f (MaybeT m) = MaybeT (f m)

我们目前没有在transformersmtl包装中提供它,因为它需要 a Rank2Type,但实现起来非常简单。

如果有足够的需求,我会很乐意将它打包成一个monad-extras包裹。

现在,我说一部分,因为虽然这回答了您帖子主题中类型给出的问题,但它并没有解决与您的问题相关的大部分文本所反映的需求!

为此,您可能需要遵循 luqui 的建议。=)

于 2011-07-06T21:03:18.820 回答
4

我相信interleavableIO 包解决了这个问题。这是在这个咖啡馆线程中讨论的。

于 2011-07-06T19:01:08.517 回答
4

monad-control包将执行此操作。我认为您想要Control.Monad.IO.Control的功能liftIOOp_

具体来说,

liftIOOp_ (withArgs newArgs) f

应该做你想做的。您也可以bracket使用该liftIOOp功能举起类似的东西。

于 2011-07-06T20:33:49.737 回答
0

看来您也可以使用它runReaderT来获得所需的效果:

*> :t withArgs [] (runReaderT f FooBar)
withArgs [] (runReaderT f FooBar) :: IO Answer

whereFooBar是一些数据构造函数,f定义如上。

于 2011-07-06T21:48:35.217 回答