Haskell:电梯vs liftIO

时间:2010-10-13 06:21:53

标签: haskell monad-transformers

在什么情况下应该使用liftIO?当我使用ErrorT String IO时,lift函数可以将IO操作提升为ErrorT,因此liftIO似乎是多余的。

3 个答案:

答案 0 :(得分:84)

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

答案 1 :(得分:31)

liftIO只是IO Monad的捷径,无论你使用的Monad是什么。基本上,liftIO等于使用可变数量的升降机。起初这可能听起来多余,但使用liftIO有一个很大的优势:它使你的IO代码独立于实际的Monad构造,所以你可以重复使用相同的代码,无论你的最终Monad构建的层数是多少(这是非常重要的)写一个monad变压器时)。

另一方面,liftIO不是免费提供的,就像电梯一样:你正在使用的Monad变压器必须支持它,例如你所在的Monad必须是MonadIO类的一个实例,但现在大多数Monad都是这样做的(当然,类型检查器会在编译时为你检查这个:这就是Haskell的优势!)。

答案 2 :(得分:1)

以前的答案都很好地解释了差异。我只是想了解一下内部工作原理,以便更容易理解liftIO并不是什么神奇的东西(对于像我这样的Haskellers新手来说)。

liftIO :: IO a -> m a

是一个明智的工具,

lift :: (Control.Monad.Trans.Class.MonadTrans t, Monad m) => m a -> t m a

最常用在底部单子为IO时使用。对于IO monad,其定义非常简单。

class (Monad m) => MonadIO m where
  liftIO :: IO a -> m a

instance MonadIO IO where
  liftIO = id

这么简单... liftIO实际上只是id单子的IO,基本上IO是类型类定义中唯一的一个

问题是,当我们有一个monad类型,该类型由IO上的多层monad变换器组成时,最好为每个monad变换器层有一个MonadIO实例。例如,MonadIO的{​​{1}}实例也要求MaybeT m属于m类型类。

基本上,编写MonadIO实例也是一项非常简单的任务。对于MonadIO,它的定义类似于

MaybeT m

instance (MonadIO m) => MonadIO (MaybeT m) where liftIO = lift . liftIO

StateT s m

它们都是一样的。想象一下,当您有一个4层变压器堆栈时,您要么需要执行instance (MonadIO m) => MonadIO (StateT s m) where liftIO = lift . liftIO ,要么只需执行lift . lift . lift . lift $ myIOAction。如果您考虑一下,每个liftIO myIOAction会将您带到堆栈的最下一层,直到它一直深入到lift . liftIO,在IO被定义为liftIO的地方并使用与上面组成的id相同的代码完成。

因此,这基本上就是为什么不考虑变压器堆栈配置的情况,只要所有底层都是liftMonadIO的成员,那么一个单独的MonadTrans就可以了。