liftIO的目的是什么?

12

我从互联网上找到了以下的代码片段:

calculateLength :: LengthMonad Int
calculateLength = do
  -- all the IO operations have to be lifted to the IO monad in the monad stack
  liftIO $ putStrLn "Please enter a non-empty string: "
  s <- liftIO getLine
  if null s
    then throwError "The string was empty!"
    else return $ length s

我不理解,为什么作者使用liftIO

liftIO的目的是什么?

它的定义如下:

class (Monad m) => MonadIO m where
    -- | Lift a computation from the 'IO' monad.
    liftIO :: IO a -> m a  

有没有可能提升 IO a -> [a]?它看起来像自然变换。


1
你在一个 MonadIO m => m a 中运行一个 IO a。例如,一个 Yesod 的 Handler - Willem Van Onsem
4
无法使用 IO a -> [a],因为 [] 不是 MonadIO 的实例。 - Willem Van Onsem
1
因为LengthMonad(https://github.com/haskell/mtl/blob/master/Control/Monad/Error.hs#L129)看起来只是一个“monad transformer”。它们旨在将IO monad包装在EitherT String中以运行错误(这就是throwError的目的)。现在,由于这是使用EitherT String IO工作的,因此我们不能仅在do块中使用IO a。我们需要一些东西将它们提升到EitherT String IO - Willem Van Onsem
4
вҖңpointвҖқзҡ„ж„ҸжҖқжҳҜputStrLn "..."ж— жі•йҖҡиҝҮзұ»еһӢжЈҖжҹҘпјҢдҪҶliftIO $ putStrLn "..."еҸҜд»ҘгҖӮжӮЁжҳҜеҗҰеңЁиҜўй—®иҝҷжҳҜдёәд»Җд№Ҳпјҹ - chepner
1
@zero_coding,“不通过类型检查”是另一种说法,意思是“未通过类型检查阶段”。也就是说,如果你在那里使用putStrLn而没有使用liftIO,你将会得到一个类型错误。 - luqui
显示剩余4条评论
1个回答

11

getLineputStrLn "..."等IO操作只能在IO单子中使用。在其他单子中使用它们会导致类型错误。

尽管如此,还有许多单子M是基于IO定义的(例如StateT Int IO和您的LengthMonad),因此它们允许将IO操作转换为M操作并执行。

但是,我们需要为每个M单子创建一种转换方法:

convertIOintoM1 :: IO a -> M1 a 
convertIOintoM2 :: IO a -> M2 a
convertIOintoM3 :: IO a -> M3 a
...

由于这很麻烦,库定义了一个类型类 MonadIO,其中包含此类转换函数,因此所有上述函数都可以被命名为 liftIO

在实践中,每当需要在另一个单子中运行 IO 操作时,通常会使用 liftIO,前提是该单子允许它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接