Reputation: 4344
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?
Upvotes: 4
Views: 715
Reputation: 13876
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"
Upvotes: 2