一个 do-block 中的多个单子

  • 本文关键字:do-block 一个 haskell
  • 更新时间 :
  • 英文 :


我目前正在尝试学习Haskell,我真的无法理解在do-block中只使用一个monad的概念。如果我有foo :: Int -> Maybe Int并且想在此函数中使用例如函数hIsEOF :: Handle -> IO Bool。有人可以解释一下我一些基本的例子,我将如何使用hIsEOF并以某种方式与Bool一起工作吗?

我一直试图在这里和谷歌上搜索,但我总是遇到一些高级的东西,基本上没有人解释如何,他们只是就如何将其正确放入 OP 的代码提供建议。我看到这些线程中提到了monad变压器,但即使阅读了很少的资源,我似乎也无法找到如何使用它们的正确方法。

使用单体变压器,您需要做的就是

  1. 将函数签名从 Int -> Maybe Int 更改为

    foo :: Int -> MaybeT IO Int
    
  2. 解除do块内的所有 IO 操作(在本例中为 liftIO)。在这里查看为什么您需要这种提升以及它究竟有什么作用。

  3. 使用 runMaybeT 运行函数

一个最小的例子是:

import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
import System.IO (openFile, hClose, hSeek, hIsEOF)
import System.IO (IOMode(ReadMode), SeekMode(AbsoluteSeek))
foo :: Int -> MaybeT IO Int
foo i = do
    h <- lift $ openFile "test.txt" ReadMode
    -- move the handle i bytes ahead
    lift . hSeek h AbsoluteSeek $ fromIntegral i
    eof <- lift $ hIsEOF h  -- check if hit end of file
    lift $ hClose h
    if eof then fail "eof!" else return i

然后

> runMaybeT $ foo 1
Just 1
> runMaybeT $ foo 100  -- would hit eof
Nothing

你从中得到的将是以下类型:

(runMaybeT . foo) :: Int -> IO (Maybe Int)

简短的回答是否定的。 do 符号基于两件事

return :: a -> m a
>>= :: m a -> (a -> m b) -> m b

请注意,在>>=中,虽然你可以使用两种不同的内部类型(ab),但它只适用于一种外部类型,一种monad(m)。m aa -> m b都是同一个单子。

更长的答案是,您必须将它们转换为相同的 monad。例如,Maybe可以转换为IO,如下所示:

maybeToIO Nothing = error "No thing"
maybeToIO (Just a) = return a

一般来说,除非在特殊情况下,否则Monads不能相互转换。


那么为什么>>=只适用于一个monad呢?好吧,看看这个。它定义为一次处理一个monad,do-notation被定义为>>=一起工作。选择这个定义的原因有些复杂,但如果有人愿意,我可以编辑它。

你可以想出自己的>>=,可以处理多个monads,然后使用可重新绑定的语法,但这可能很难。

最新更新