Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple monads in one do-block

Tags:

haskell

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.

like image 357
Marek Milkovič Avatar asked Feb 24 '16 02:02

Marek Milkovič


2 Answers

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.

like image 115
PyRulez Avatar answered Nov 01 '22 22:11

PyRulez


With monad transformers, all you need to do is

  1. change the function signature from Int -> Maybe Int to

    foo :: Int -> MaybeT IO Int
    
  2. 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.

  3. 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)
like image 32
behzad.nouri Avatar answered Nov 01 '22 23:11

behzad.nouri