Lac*_*lan 76 haskell monad-transformers
在什么情况下应该liftIO使用?当我使用时ErrorT String IO,该lift功能可以解除IO操作ErrorT,因此liftIO看起来多余.
Rom*_*aka 88
lift总是从"前一个"层提升.如果你需要从第二层升起,你需要lift . lift等等.
另一方面,liftIO总是从IO层(当存在时,总是在堆栈的底部)提升.所以,如果你有超过2层的monad,你会很感激liftIO.
比较以下lambdas中的参数类型:
type T = ReaderT Int (WriterT String IO) Bool
> :t \x -> (lift x :: T)
\x -> (lift x :: T) :: WriterT String IO Bool -> T
> :t \x -> (liftIO x :: T)
\x -> (liftIO x :: T) :: IO Bool -> T
Run Code Online (Sandbox Code Playgroud)
Cri*_*ris 35
liftIO只是IO Monad的捷径,无论你使用的Monad是什么.基本上,liftIO等于使用可变数量的升降机.起初这可能听起来多余,但使用liftIO有一个很大的优势:它使你的IO代码独立于实际的Monad构造,所以你可以重复使用相同的代码,无论你的最终Monad构建的层数是多少(这是非常重要的)写一个monad变压器时).
在这个问题上,liftIO不是免费提供的,就像电梯一样:你正在使用的Monad变压器必须支持它,例如你所在的Monad必须是MonadIO类的一个实例,但现在大多数Monads都是(当然,类型检查器会在编译时为您检查这个:这就是Haskell的优势!).
之前的答案都很好地解释了这种差异。我只是想阐明其内部工作原理,以便更容易理解它为什么liftIO不神奇(对于像我这样的 Haskellers 新手)。
liftIO :: IO a -> m a
Run Code Online (Sandbox Code Playgroud)
是一个明智的工具,只是建立在
lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a
Run Code Online (Sandbox Code Playgroud)
当底部单子是 时最常使用IO。对于IOmonad 来说,它的定义非常简单。
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
Run Code Online (Sandbox Code Playgroud)
这个简单的...liftIO实际上只是id针对IOmonad 的,并且基本上IO是类型类定义中的唯一一个。
问题是,当我们有一个由多层 monad 转换器组成的 monad 类型时IO,我们最好MonadIO为每一层 monad 转换器都有一个实例。例如,MonadIO的实例也MaybeT m需要m属于类型类。MonadIO
编写MonadIO实例基本上也是一项非常简单的任务。因为MaybeT m它的定义如下
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
Run Code Online (Sandbox Code Playgroud)
或为了StateT s m
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
Run Code Online (Sandbox Code Playgroud)
他们都是一样的。想象一下,当您有一个 4 层变压器堆栈时,您要么需要执行操作lift . lift . lift . lift $ myIOAction,要么只需执行liftIO myIOAction. 如果您考虑一下,每个lift . liftIO都会将您带到堆栈中的下一层,直到它一直向下挖掘到定义为的IO位置,并使用与上面的composed相同的代码完成。liftIOidlift
因此,这基本上就是为什么无论变压器堆栈配置如何,只要所有底层都是其中的成员MonadIO并且MonadTrans单个liftIO就可以了。