有没有比在嵌套的 `MonadTrans` 中的每个 `IO` 之前写 `lift` 更好的方法?
Is there a better way than writing `lift`s before each `IO` in a nested `MonadTrans`?
有时我需要使用多个嵌套MonadTrans
。例如,我会在 ExceptT
中放置一个 MaybeT
,以模仿命令式编程中的 continue
和 break
:
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
实例,这就是为什么这适用于任何深度的堆栈。
有时我需要使用多个嵌套MonadTrans
。例如,我会在 ExceptT
中放置一个 MaybeT
,以模仿命令式编程中的 continue
和 break
:
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
实例,这就是为什么这适用于任何深度的堆栈。