有没有比在嵌套的 `MonadTrans` 中的每个 `IO` 之前写 `lift` 更好的方法?

Is there a better way than writing `lift`s before each `IO` in a nested `MonadTrans`?

有时我需要使用多个嵌套MonadTrans。例如,我会在 ExceptT 中放置一个 MaybeT,以模仿命令式编程中的 continuebreak

runExceptT . forM [1..] $ \ _ -> runMaybeT $ do
    ...
    mzero   -- this mimics continue
    lift $ throwE "..."    -- this mimics break
    lift . lift $ putStrLn "Hello!"
    ...

然而,正如上面的代码所示,每次我需要在这个 "artificial loop" 中做任何 IO 时,我需要在它之前放一个丑陋的 lift . lift。想象一下,如果我有更复杂的嵌套和很多 IO 操作,这很快就会变得很烦人。如何让代码更干净简洁?

对于 IO 的特殊情况,您可以使用 liftIO :: MonadIO m => IO a -> m a 通过任意深度的 monad 转换器堆栈提升 IO 操作:

liftIO $ putStrLn "Hello"

此表达式的类型为 MonadIO m => m ()。任何在其内部某处包含 MonadIO 实例的转换器堆栈本身应该有一个 MonadIO 实例,这就是为什么这适用于任何深度的堆栈。