Question

I have trouble gripping to monads and monad transformers. I have the following contrived example (not compilable):

import Control.Monad
import Control.Monad.Error
import Control.Monad.Reader

data State = State Int Int Int
type Foo = ReaderT State IO

readEither :: String -> Either String Int
readEither s = let p = reads s
           in case p of
               [] -> throwError "Could not parse"
               [(a, _)] -> return a

readEitherT :: IO (Either String Int)
readEitherT = let p s = reads s
          in runErrorT $ do
    l <- liftIO (getLine)
    readEither l

foo :: Foo Int
foo = do
  d <- liftIO $ readEitherT
  case d of
       Right dd -> return dd
       Left em -> do
     liftIO $ putStrLn em
     return (-1)

bar :: Foo String
bar = do
  liftIO $ getLine

defaultS = State 0 0 0

If I copy the functionality of readEither to readEitherT, it works, but I have a nagging feeling that I can leverage the power of the existing readEither function, but I can't figure out how. If I try to lift the readEither in the readEitherT function, it lifts it to ErrorT String IO (Either String Int) as it should. But I should somehow get it to ErrorT String IO Int.

If I'm going to the wrong direction with this, what is the correct way to handle errors which require IO (or other monads) and are to be called from monadic context (see the foo function in the example)

Edit: Apparently it was not clear what I was trying to do. Maybe the following function describes what and why I was wondering

maybePulseQuit :: Handle -> IO (Either String ())
maybePulseQuit h = runErrorT $ do
  f <- liftIO $ (communicate h "finished" :: IO (Either String Bool))
  (ErrorT . pure) f >>= \b → liftIO $ when b $ liftIO pulseQuit

This works, but is still ugly because of the binds. This is a lot clearer than the previous version which had case checking. Is this the recommended way to do this?

Was it helpful?

Solution

It is not clear why you need ErrorT. You can implement readEitherT like

readEitherT :: IO (Either String Int)
readEitherT = fmap readEither getLine

If you really need ErrorT for some reason, then you can create utility function eitherToErrorT:

eitherToErrorT = ErrorT . pure

readEitherT = runErrorT $ do
  l <- liftIO $ getLine
  eitherToErrorT $ readEither l

[ADD] Maybe you just want to add ErrorT into your monad stack...

data State = State Int Int Int
type Foo = ErrorT String (ReaderT State IO)

runFoo :: Foo a -> State -> IO (Either String a)
runFoo foo s = runReaderT (runErrorT foo) s

doIt :: Int -> Foo Int
doIt i = if i < 0
            then throwError "i < 0"
            else return (i * 2)

Example:

*Main> runFoo (doIt 1 >>= doIt) (State 0 0 0)
Right 4
*Main> runFoo (doIt (-1) >>= doIt) (State 0 0 0)
Left "i < 0"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top