I have a function which connects to a database, then runs a query. Each of these steps results in IO (Either SomeErrorType SomeResultType)
.
One of the things I have really liked about using Either
and similar monads in learning Haskell has been the ability to use the monad functions like >>=
and combinators like mapLeft
to streamline a lot of the handling of expected error states.
My expectation here from reading blog posts, the Control.Monad.Trans
documentation, and other answers on SO is that I have to somehow use transformers / lift to move from the IO
context to the Either
context.
This answer in particular is really good, but I'm struggling to apply it to my own case.
A simpler example of my code:
simpleVersion :: Integer -> Config -> IO ()
simpleVersion id c =
connect c >>= \case
(Left e) -> printErrorAndExit e
(Right conn) -> (run . query id $ conn)
>>= \case
(Left e) -> printErrorAndExit e
(Right r) -> print r
>> release conn
My problem is that (a) I'm not really understanding the mechanics of how ExceptT
gets me to a similar place to the mapLeft handleErrors $ eitherErrorOrResult >>= someOtherErrorOrResult >>= print
world; (b) I'm not sure how to ensure that the connection is always released in the nicest way (even in my simple example above), although I suppose I would use the bracket pattern.
I'm sure every (relatively) new Haskeller says this but I still really don't understand monad transformers and everything I read (except aforelinked SO answer) is too opaque for me (yet).
How can I transform the code above into something which removes all this nesting and error handling?