我目前正在尝试学习Haskell,我真的无法理解在do-block中只使用一个monad的概念。如果我有foo :: Int -> Maybe Int
并希望在此函数中使用例如函数hIsEOF :: Handle -> IO Bool
。有人可以在一些基本示例中向我解释如何使用hIsEOF
并以某种方式使用Bool
?
我一直在尝试在这里和谷歌搜索,但我总是遇到一些高级的东西,基本上没有人解释如何,他们只是提供如何适应OP的代码的建议。我看到那些线程中提到的monad变换器,但即使在阅读了一些资源之后,我似乎找不到如何使用它们的正确方法。
答案 0 :(得分:12)
使用monad变换器,您需要做的就是
将函数签名从Int -> Maybe Int
更改为
foo :: Int -> MaybeT IO Int
lift do
块内的所有IO操作(在这种情况下为liftIO
)。请参阅here why you need this lifting and what exactly it does。
使用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)
答案 1 :(得分:7)
简短的回答是否定的。记谱法基于两件事
return :: a -> m a
>>= :: m a -> (a -> m b) -> m b
请注意>>=
虽然您可以使用两种不同的内部类型(a
和b
),但它只适用于一种外部类型,一种monad(m
)。 m a
和a -> m b
都是同一个monad。
答案越长,你必须将它们转换为同一个monad。例如,Maybe
可以像这样转换为IO
:
maybeToIO Nothing = error "No thing"
maybeToIO (Just a) = return a
一般情况下,Monads不能相互转换,除非在特殊情况下。
那么为什么>>=
只适用于一个monad?那么看看this。它是定义的,以便一次使用单个monad,并且定义符号以使用>>=
。选择这个定义的原因有点复杂,但如果有人想要,我可以编辑它。
您可以提出自己的>>=
,它适用于多个monad,然后使用rebindable syntax,但这可能很难。