hliu
hliu

Reputation: 1047

how to use IO Monad in another Monad

I use MongoDB library to handle data from Mongodb. There is a Monad called Action representing a DB read or write operation https://github.com/TonyGen/mongoDB-haskell/blob/master/doc/tutorial.md. But, I find that when I in monad Action, I also want to do some IO which must be in an IO Monad. Some codes like

-- `Action' is a Monad
--
intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- liftIO $ openFile ric AppendMode
  liftIO $ hPutStrLn outH "Some log"
  loopIntoFile outH c
  liftIO $ hClose outH

There is a liftIO before any IO monad and I think it may be verbose. Any concise way to handle this ?

Upvotes: 0

Views: 740

Answers (2)

Jon Purdy
Jon Purdy

Reputation: 55069

You can’t avoid the liftIO, unfortunately, because the standard IO actions are not overloaded to work in any MonadIO. But you can join sequences of IO actions under one call to liftIO:

intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- liftIO $ do
    openFile ric AppendMode
    hPutStrLn outH "Some log"
  loopIntoFile outH c
  liftIO $ hClose outH

Or, if you intend to use the same IO operations repeatedly, you can introduce auxiliary definitions for them:

intoFile :: String -> Cursor -> Action IO ()
intoFile ric c = do
  outH <- openLog ric AppendMode
  log outH "Some log"
  loopIntoFile outH c
  closeLog outH

openLog path mode = liftIO (openFile path mode)
log handle message = liftIO (hPutStrLn handle message)
closeLog handle = liftIO (hClose handle)

Upvotes: 3

Arthur Welf
Arthur Welf

Reputation: 176

You want to carry along 2 additional contexts - the IO context and Action context. That's the case for monad transformers, because they allow you to deal with layering monads and choose inside of do block which monad to choose for desired action. Here is a great explanation of why we need them and how to use them.

Upvotes: 0

Related Questions