I am currently trying to learn Haskell and I really cannot understand the concept of using just one one monad in do-block. If I have foo :: Int -> Maybe Int
and want to use for example function hIsEOF :: Handle -> IO Bool
in this function. Can someone please explain me on some basic example how would I use hIsEOF
and could somehow work with Bool
?
I have been trying to search here and on google, but I always run into some advanced stuff where basically noone explains how, they just give advice on how to fit it right into OP's code. I saw monad transformers mentioned in those threads but even after reading few resources, I cannot seem to find the right way on how to use them.
The short answer is no. do notation is based on two things
return :: a -> m a
>>= :: m a -> (a -> m b) -> m b
Notice in >>=
that although you can work with two different inner types (a
and b
), it only works with one outer type, one monad (m
). Both m a
and a -> m b
are the same monad.
The longer answer is, you have to convert them to the same monad. For example, Maybe
can be converted to IO
like so:
maybeToIO Nothing = error "No thing"
maybeToIO (Just a) = return a
Monads, in general, can not be converted into each other though, except in special cases.
So why does >>=
only work with one monad? Well just look at this. It is defined so as to work with a single monad at a time, and do-notation is defined to work with >>=
. The reasons why this definition was chosen is somewhat complicated, but I can edit it in if someone wants.
You could come up with your own >>=
that works with multiple monads, and then use rebindable syntax, but this would probably be hard.
With monad transformers, all you need to do is
change the function signature from Int -> Maybe Int
to
foo :: Int -> MaybeT IO Int
lift all the IO actions inside the do
block (or liftIO
in this case). See here why you need this lifting and what exactly it does.
run the function using runMaybeT
A minimal example would be:
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
then,
\> runMaybeT $ foo 1
Just 1
\> runMaybeT $ foo 100 -- would hit eof
Nothing
what you get out of this would be of the type:
(runMaybeT . foo) :: Int -> IO (Maybe Int)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With