我目前正在尝试学习Haskell,我真的无法理解在do-block中只使用一个monad的概念。如果我有foo :: Int -> Maybe Int
并且想在此函数中使用例如函数hIsEOF :: Handle -> IO Bool
。有人可以解释一下我一些基本的例子,我将如何使用hIsEOF
并以某种方式与Bool
一起工作吗?
我一直试图在这里和谷歌上搜索,但我总是遇到一些高级的东西,基本上没有人解释如何,他们只是就如何将其正确放入 OP 的代码提供建议。我看到这些线程中提到了monad变压器,但即使阅读了很少的资源,我似乎也无法找到如何使用它们的正确方法。
使用单体变压器,您需要做的就是
-
将函数签名从
Int -> Maybe Int
更改为foo :: Int -> MaybeT IO Int
-
解除
do
块内的所有 IO 操作(在本例中为liftIO
)。在这里查看为什么您需要这种提升以及它究竟有什么作用。 -
使用
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
请注意,在>>=
中,虽然你可以使用两种不同的内部类型(a
和b
),但它只适用于一种外部类型,一种monad(m
)。m a
和a -> m b
都是同一个单子。
更长的答案是,您必须将它们转换为相同的 monad。例如,Maybe
可以转换为IO
,如下所示:
maybeToIO Nothing = error "No thing"
maybeToIO (Just a) = return a
一般来说,除非在特殊情况下,否则Monads不能相互转换。
那么为什么>>=
只适用于一个monad呢?好吧,看看这个。它被定义为一次处理一个monad,do-notation被定义为与>>=
一起工作。选择这个定义的原因有些复杂,但如果有人愿意,我可以编辑它。
你可以想出自己的>>=
,可以处理多个monads,然后使用可重新绑定的语法,但这可能很难。