Right now my code looks like this:
postUser :: ServerPart Response
postUser = do
-- parseBody :: ServerPart (Maybe User)
parsedUser <- parseBody
case parsedUser of
Just user -> do
-- saveUser :: User -> ServerPart (Maybe (Key User))
savedUser <- saveUser user
case savedUser of
Just user -> successResponse
Nothing -> errorResponse "user already exists"
Nothing -> errorResponse "couldn't parse user"
Which works, but I know there's a way to avoid the nested pattern matching. I thought this was what bind would do for me, so I tried
parseUser :: ServerPart (Maybe User)
addUser :: User -> ServerPart (Maybe ())
case parseUser >>= saveUser of
Just _ -> success
Nothing -> error
and
savedUser <- (parseUser >>= saveUser)
case savedUser of
Just _ -> success
Nothing -> error
But I get the following error:
Couldn't match type ‘Maybe a0’ with ‘User’
Expected type: Maybe a0 -> ServerPartT IO (Maybe (Key User))
Actual type: User -> ServerPart (Maybe (Key User))
In the second argument of ‘(>>=)’, namely ‘saveUser’
In the expression: parseBody >>= saveUser
which I take to mean >>= is applying saveUser to the Maybe User instead of the User that I need it to, and I'm not sure how to finagle the types to match.
How can I rewrite this to avoid the nested pattern matching?
While I would argue that the original way you have it written is the most readable approach, taking this as an exercise, the MaybeT monad transformer is what you are looking for.
The problem you are running into is that your are trying to jump between the ServerPart monad and the Maybe monad. This is why you can't directly bind parseBody and saveUser. Monad transformers allow you to combine monads to avoid this problem.
import Control.Monad.Trans.Maybe
postUser :: ServerPart Response
postUser = do
-- parseBody :: MaybeT ServerPart User
-- saveUser :: User -> MaybeT ServerPart (Key User)
user <- runMaybeT $ parseBody >>= saveUser
case user of
Just _ -> successResponse
Nothing -> errorResponse "Error saving user"
You will need to refactor your parseBody and saveUser functions to use the MaybeT monad. Since I can't see these functions, I can't help you there, but it can usually be done easily using lift from Control.Applicative.
Useful links on monad transformers:
https://en.wikibooks.org/wiki/Haskell/Monad_transformers
https://www.schoolofhaskell.com/user/commercial/content/monad-transformers
EDIT: MaybeT for parseBody
parseBody :: FromJSON a => MaybeT ServerPart a
parseBody = MaybeT $ fmap A.decode getBody
And just as a general tip: bar >>= (return . f) is equivalent to fmap f bar. The latter being cleaner and more general since it does not require the monad instance.
Related
I just start coding in Haskell recently and I start getting use of do block. I'm coming from Scala world, and after read a book and some blogs I see that do block was the inspiration of our from comprehension. But still I´m struggling with the arguments that pass in every function as input -> output
Here in my code I'm using scotty http server library to get a request and I'm trying to persist that request in mySQL.
But in this line where I try top get the value from the Maybe to send to the another function to be persisted
user <- (fmap (\user -> insertUser user) maybeUser)
It never compile since the function does not return the expected type
ActionT Text IO (Maybe User)
but
IO User
Here my hole program
createUser :: ActionM ()
createUser = do maybeUser <- getUserParam
-- Persist the user
_ <- persistUser
json maybeUser
getUserParam :: ActionT Text IO (Maybe User)
getUserParam = do requestBody <- body
return (decode requestBody)
persistUser :: Maybe User -> ActionT Text IO (Maybe User)
persistUser _maybeUser = let maybeUser = _maybeUser in do
user <- maybeUser
user <- (fmap (\user -> insertUser user) maybeUser)
return maybeUser
insertUser :: User -> IO User
insertUser _user = let user = _user in do
conn <- createConnection
status <- execute conn insertUserQuery [MySQLInt32 (intToInt32 $ getUserId user), MySQLText "hello_haskell_world"]
return user
Let's consider the following function:
persistUser :: Maybe User -> ActionT Text IO (Maybe User)
A value of type Maybe User is passed as an argument, and we need this user to be inserted into database. In order to do that, we can use (<$>) (or fmap) function as:
insertUser <$> maybeUser
The resulting type is: Maybe (IO User). Now we need to lift this type to ActionT Text IO (Maybe User) somehow.
Web.Scotty.Trans has a liftAndCatchIO function (also available in Web.Scotty module), which mostly does what we need, but it accepts IO a as an argument, so we need to "swap" Maybe and IO. Let's find a function for this. So sequence does what we need.
As a result, we have the following implementation of persistUser function:
persistUser maybeUser =
liftAndCatchIO $ sequence $ insertUser <$> maybeUser
Could someone give a super simple (few lines) monad transformer example, which is non-trivial (i.e. not using the Identity monad - that I understand).
For example, how would someone create a monad that does IO and can handle failure (Maybe)?
What would be the simplest example that would demonstrate this?
I have skimmed through a few monad transformer tutorials and they all seem to use State Monad or Parsers or something complicated (for a newbee). I would like to see something simpler than that. I think IO+Maybe would be simple, but I don't really know how to do that myself.
How could I use an IO+Maybe monad stack?
What would be on top? What would be on bottom? Why?
In what kind of use case would one want to use the IO+Maybe monad or the Maybe+IO monad? Would that make sense to create such a composite monad at all? If yes, when, and why?
This is available here as a .lhs file.
The MaybeT transformer will allow us to break out of a monad computation much like throwing an exception.
I'll first quickly go over some preliminaries. Skip down to Adding Maybe powers to IO for a worked example.
First some imports:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
Rules of thumb:
In a monad stack IO is always on the bottom.
Other IO-like monads will also, as a rule, always appear on the bottom, e.g. the state transformer monad ST.
MaybeT m is a new monad type which adds the power of the Maybe monad to the monad m - e.g. MaybeT IO.
We'll get into what that power is later. For now, get used to thinking of MaybeT IO as the maybe+IO monad stack.
Just like IO Int is a monad expression returning an Int, MaybeT IO Int is a MaybeT IO expression returning an Int.
Getting used to reading compound type signatures is half the battle to understanding monad transformers.
Every expression in a do block must be from the same monad.
I.e. this works because each statement is in the IO-monad:
greet :: IO () -- type:
greet = do putStr "What is your name? " -- IO ()
n <- getLine -- IO String
putStrLn $ "Hello, " ++ n -- IO ()
This will not work because putStr is not in the MaybeT IO monad:
mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? " -- IO monad - need MaybeT IO here
...
Fortunately there is a way to fix this.
To transform an IO expression into a MaybeT IO expression use liftIO.
liftIO is polymorphic, but in our case it has the type:
liftIO :: IO a -> MaybeT IO a
mgreet :: MaybeT IO () -- types:
mgreet = do liftIO $ putStr "What is your name? " -- MaybeT IO ()
n <- liftIO getLine -- MaybeT IO String
liftIO $ putStrLn $ "Hello, " ++ n -- MaybeT IO ()
Now all of the statement in mgreet are from the MaybeT IO monad.
Every monad transformer has a "run" function.
The run function "runs" the top-most layer of a monad stack returning
a value from the inside layer.
For MaybeT IO, the run function is:
runMaybeT :: MaybeT IO a -> IO (Maybe a)
Example:
ghci> :t runMaybeT mgreet
mgreet :: IO (Maybe ())
ghci> runMaybeT mgreet
What is your name? user5402
Hello, user5402
Just ()
Also try running:
runMaybeT (forever mgreet)
You'll need to use Ctrl-C to break out of the loop.
So far mgreet doesn't do anything more than what we could do in IO.
Now we'll work on an example which demonstrates the power of mixing
the Maybe monad with IO.
Adding Maybe powers to IO
We'll start with a program which asks some questions:
askfor :: String -> IO String
askfor prompt = do
putStr $ "What is your " ++ prompt ++ "? "
getLine
survey :: IO (String,String)
survey = do n <- askfor "name"
c <- askfor "favorite color"
return (n,c)
Now suppose we want to give the user the ability to end the survey
early by typing END in response to a question. We might handle it
this way:
askfor1 :: String -> IO (Maybe String)
askfor1 prompt = do
putStr $ "What is your " ++ prompt ++ " (type END to quit)? "
r <- getLine
if r == "END"
then return Nothing
else return (Just r)
survey1 :: IO (Maybe (String, String))
survey1 = do
ma <- askfor1 "name"
case ma of
Nothing -> return Nothing
Just n -> do mc <- askfor1 "favorite color"
case mc of
Nothing -> return Nothing
Just c -> return (Just (n,c))
The problem is that survey1 has the familiar staircasing issue which
doesn't scale if we add more questions.
We can use the MaybeT monad transformer to help us here.
askfor2 :: String -> MaybeT IO String
askfor2 prompt = do
liftIO $ putStr $ "What is your " ++ prompt ++ " (type END to quit)? "
r <- liftIO getLine
if r == "END"
then MaybeT (return Nothing) -- has type: MaybeT IO String
else MaybeT (return (Just r)) -- has type: MaybeT IO String
Note how all of the statemens in askfor2 have the same monad type.
We've used a new function:
MaybeT :: IO (Maybe a) -> MaybeT IO a
Here is how the types work out:
Nothing :: Maybe String
return Nothing :: IO (Maybe String)
MaybeT (return Nothing) :: MaybeT IO String
Just "foo" :: Maybe String
return (Just "foo") :: IO (Maybe String)
MaybeT (return (Just "foo")) :: MaybeT IO String
Here return is from the IO-monad.
Now we can write our survey function like this:
survey2 :: IO (Maybe (String,String))
survey2 =
runMaybeT $ do a <- askfor2 "name"
b <- askfor2 "favorite color"
return (a,b)
Try running survey2 and ending the questions early by typing END as a response to either question.
Short-cuts
I know I'll get comments from people if I don't mention the following short-cuts.
The expression:
MaybeT (return (Just r)) -- return is from the IO monad
may also be written simply as:
return r -- return is from the MaybeT IO monad
Also, another way of writing MaybeT (return Nothing) is:
mzero
Furthermore, two consecutive liftIO statements may always combined into a single liftIO, e.g.:
do liftIO $ statement1
liftIO $ statement2
is the same as:
liftIO $ do statement1
statement2
With these changes our askfor2 function may be written:
askfor2 prompt = do
r <- liftIO $ do
putStr $ "What is your " ++ prompt ++ " (type END to quit)?"
getLine
if r == "END"
then mzero -- break out of the monad
else return r -- continue, returning r
In a sense, mzero becomes a way of breaking out of the monad - like throwing an exception.
Another example
Consider this simple password asking loop:
loop1 = do putStr "Password:"
p <- getLine
if p == "SECRET"
then return ()
else loop1
This is a (tail) recursive function and works just fine.
In a conventional language we might write this as a infinite while loop with a break statement:
def loop():
while True:
p = raw_prompt("Password: ")
if p == "SECRET":
break
With MaybeT we can write the loop in the same manner as the Python code:
loop2 :: IO (Maybe ())
loop2 = runMaybeT $
forever $
do liftIO $ putStr "Password: "
p <- liftIO $ getLine
if p == "SECRET"
then mzero -- break out of the loop
else return ()
The last return () continues execution, and since we are in a forever loop, control passes back to the top of the do block. Note that the only value that loop2 can return is Nothing which corresponds to breaking out of the loop.
Depending on the situation you might find it easier to write loop2 rather than the recursive loop1.
Suppose you have to work with IO values that "may fail" in some sense, like foo :: IO (Maybe a), func1 :: a -> IO (Maybe b) and func2 :: b -> IO (Maybe c).
Manually checking for the presence of errors in a chain of binds quickly produces the dreaded "staircase of doom":
do
ma <- foo
case ma of
Nothing -> return Nothing
Just a -> do
mb <- func1 a
case mb of
Nothing -> return Nothing
Just b -> func2 b
How to "automate" this in some way? Perhaps we could devise a newtype around IO (Maybe a) with a bind function that automatically checks if the first argument is a Nothing inside IO, saving us the trouble of checking it ourselves. Something like
newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }
With the bind function:
betterBind :: MaybeOverIO a -> (a -> MaybeOverIO b) -> MaybeOverIO b
betterBind mia mf = MaybeOverIO $ do
ma <- runMaybeOverIO mia
case ma of
Nothing -> return Nothing
Just a -> runMaybeOverIO (mf a)
This works! And, looking at it more closely, we realize that we aren't using any particular functions exclusive to the IO monad. Generalizing the newtype a little, we could make this work for any underlying monad!
newtype MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }
And this is, in essence, how the MaybeT transformer works. I have left out a few details, like how to implement return for the transformer, and how to "lift" IO values into MaybeOverM IO values.
Notice that MaybeOverIO has kind * -> * while MaybeOverM has kind (* -> *) -> * -> * (because its first "type argument" is a monad type constructor, that itself requires a "type argument").
Sure, the MaybeT monad transformer is:
newtype MaybeT m a = MaybeT {unMaybeT :: m (Maybe a)}
We can implement its monad instance as so:
instance (Monad m) => Monad (MaybeT m) where
return a = MaybeT (return (Just a))
(MaybeT mmv) >>= f = MaybeT $ do
mv <- mmv
case mv of
Nothing -> return Nothing
Just a -> unMaybeT (f a)
This will allow us to perform IO with the option of failing gracefully in certain circumstances.
For instance, imagine we had a function like this:
getDatabaseResult :: String -> IO (Maybe String)
We can manipulate the monads independently with the result of that function, but if we compose it as so:
MaybeT . getDatabaseResult :: String -> MaybeT IO String
We can forget about that extra monadic layer, and just treat it as a normal monad.
I am writing a small library for interacting with a few external APIs. One set of functions will construct a valid request to the yahoo api and parse the result to a data type. Another set of functions will look up the users current location based on IP and return a data type representing the current location. While the code works, it seems having to explicitly pattern match to sequence multiple functions of type IO (Maybe a).
-- Yahoo API
constructQuery :: T.Text -> T.Text -> T.Text
constructQuery city state = "select astronomy, item.condition from weather.forecast" <>
" where woeid in (select woeid from geo.places(1)" <>
" where text=\"" <> city <> "," <> state <> "\")"
buildRequest :: T.Text -> IO ByteString
buildRequest yql = do
let root = "https://query.yahooapis.com/v1/public/yql"
datatable = "store://datatables.org/alltableswithkeys"
opts = defaults & param "q" .~ [yql]
& param "env" .~ [datatable]
& param "format" .~ ["json"]
r <- getWith opts root
return $ r ^. responseBody
run :: T.Text -> IO (Maybe Weather)
run yql = buildRequest yql >>= (\r -> return $ decode r :: IO (Maybe Weather))
-- IP Lookup
getLocation:: IO (Maybe IpResponse)
getLocation = do
r <- get "http://ipinfo.io/json"
let body = r ^. responseBody
return (decode body :: Maybe IpResponse)
-- Combinator
runMyLocation:: IO (Maybe Weather)
runMyLocation = do
r <- getLocation
case r of
Just ip -> getWeather ip
_ -> return Nothing
where getWeather = (run . (uncurry constructQuery) . (city &&& region))
Is it possible to thread getLocation and run together without resorting to explicit pattern matching to "get out" of the Maybe Monad?
You can happily nest do blocks that correspond to different monads, so it's just fine to have a block of type Maybe Weather in the middle of your IO (Maybe Weather) block.
For example,
runMyLocation :: IO (Maybe Weather)
runMyLocation = do
r <- getLocation
return $ do ip <- r; return (getWeather ip)
where
getWeather = run . (uncurry constructQuery) . (city &&& region)
This simple pattern do a <- r; return f a indicates that you don't need the monad instance for Maybe at all though - a simple fmap is enough
runMyLocation :: IO (Maybe Weather)
runMyLocation = do
r <- getLocation
return (fmap getWeather r)
where
getWeather = run . (uncurry constructQuery) . (city &&& region)
and now you see that the same pattern appears again, so you can write
runMyLocation :: IO (Maybe Weather)
runMyLocation = fmap (fmap getWeather) getLocation
where
getWeather = run . (uncurry constructQuery) . (city &&& region)
where the outer fmap is mapping over your IO action, and the inner fmap is mapping over your Maybe value.
I misinterpreted the type of getWeather (see comment below) such that you will end up with IO (Maybe (IO (Maybe Weather))) rather than IO (Maybe Weather).
What you need is a "join" through a two layer monad stack. This is essentially what a monad transformer provides for you (see #dfeuer's answer) but it is possible to write this combinator manually in the case of Maybe -
import Data.Maybe (maybe)
flatten :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a)
flatten m = m >>= fromMaybe (return Nothing)
in which case you can write
runMyLocation :: IO (Maybe Weather)
runMyLocation = flatten $ fmap (fmap getWeather) getLocation
where
getWeather = run . (uncurry constructQuery) . (city &&& region)
which should have the correct type. If you are going to chain multiple functions like this, you will need multiple calls to flatten, in which case it maybe be easier to build a monad transformer stack instead (with the caveat's in #dfeuer's answer).
There is probably a canonical name for the function I've called "flatten" in the transformers or mtl libraries, but I can't find it at the moment.
Note that the function fromMaybe from Data.Maybe essentially does the case analysis for you, but abstracts it into a function.
Some consider this an anti-pattern, but you could use MaybeT IO a instead of IO (Maybe a). The problem is that you only deal with one of the ways getLocation can fail—it could also throw an IO exception. From that perspective, you might as well drop the Maybe and just throw your own exception if decoding fails, catching it wherever you like.
change getWeather to have Maybe IpResponse->IO.. and use >>= to implement it and then you can do getLocation >>= getWeather. The >>= in getWeather is the one from Maybe, that will deal with Just and Nothing and the other getLocation>>= getWeather the one from IO.
you can even abstract from Maybe and use any Monad: getWeather :: Monad m -> m IpResponse -> IO .. and will work.
This is a follow-up to my previous post. MaybeT and Transactions in runDb
I thought this will be a simple thing to do but I have been trying to figure this out for over a day and still haven't made much progress. So thought I will give up and ask!
I just added a try function (from Control.Exception.Lifted) to my previous code and I couldn't get the code to type check. Variants like catch and handle had similar issues.
eauth <- LiftIO (
try( runDb $ do
ma <- runMaybeT $ do
valid <- ...
case ma of
Just a -> return a
Nothing -> liftIO $ throwIO MyException
) :: IO (Either MyException Auth)
)
case eauth of
Right auth -> return auth
Left _ -> lift $ left err400 { errBody = "Could not create user"}
My runDb looks like this (I also tried a variant where I removed liftIO):
runDb query = do
pool <- asks getPool
liftIO $ runSqlPool query pool
I get this error:
No instance for (Control.Monad.Reader.Class.MonadReader Config IO)
arising from a use of ‘runDb’
In the expression: runDb
In the first argument of ‘try’, namely
‘(runDb
$ do { ma <- runMaybeT ...
I am running inside servant handler and my return type is AppM Auth where
type AppM = ReaderT Config (EitherT ServantErr IO)
I have tried many combinations of lifting but doesn't seem to be helping. I thought I will take this opportunity to figure out things from scratch and I hit a wall as well. If someone could suggest how you arrived at the answer, it will be super instructive for me.
This has been my thought process:
I see runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a
So that seems to imply it will be in the IO monad, which means try should work
I think check the definition of MonadBaseControl which has class MonadBase b m => MonadBaseControl b m | m -> b. At this point I am confused. This functional dependency logic seems to be suggest type m dictates what b will be but in the previous one b was specified as IO.
I check MonadBase and that did not give me any clue either.
I check SqlPersistT and got no clues either.
I reduced the problem to something very simple like result <- liftIO (try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)) and that worked. So I was even more confused at this time. Doesn't runDb work in IO so shouldn't the same thing work for my original code?
I thought I can figure this out by backtracking but it seems like my level of Haskell knowledge is just not sufficient to get at the root of the problem. Appreciate if people can provide step by step pointers as to arrive at the right solution.
Thanks!
General type signature for try:
(MonadBaseControl IO m, Exception e) => m a -> m (Either e a)
Specialized type signature for try (as it appears in your code):
IO Auth -> IO (Either MyException Auth)
So, the monadic value that is the argument to try has type:
IO Auth
Everything listed above, you probably already understood. If we look at the type signature for your runDb, we get this:
runDb :: (MonadReader Config m, MonadIO m) => SqlPersistT m a -> m a
I sort of had to guess because you didn't provide a type signature, but that is probably what it is. So now, the problem should be a little clearer. You are trying to use runDb to create a monadic value for something that's supposed to be in IO. But IO doesn't satisfy the MonadReader Config instance that you need.
To make the mistake more clear, let's make runDb more monomorphic. You could give it this type signature instead:
type AppM = ReaderT Config (EitherT ServantErr IO)
runDb :: SqlPersistT AppM a -> AppM a
And now if you tried to compile your code, you would get an even better error. Instead of telling you
No instance for (Control.Monad.Reader.Class.MonadReader Config IO)
It would tell you that IO doesn't match AppM (although it would probably expand the type synonym). Practically, what this means is that you can't get the shared pool of database connections magically out of IO. You need the ReaderT Config that was passing it around everywhere.
The easiest fix I can think of would be to stop using exceptions where they aren't necessary:
mauth <- runDb $ runMaybeT $ do
... -- Same stuff you were doing earlier
case mauth of
Just auth -> return auth
Nothing -> lift $ left err400 { errBody = "Could not create user"}
I am practicing at getting consistent with my error handling, and I keep hoping to see the code that I've written start shrinking. But I built up a domain-meaningful persistence function, and the amount of code I had to write just to do monad handling and custom error handling is astounding.
For "programming errors", I just call error "assertion blown"
For really mundane things, I return Nothing (the requested object doesn't exist)
For errors that should be handled, I'm returning Either E V or its equivalent by creating a Control.Monad.Error instance to handle it.
I have in my application multiple functions which I would call primitives, but they can catch certain errors and will raise them my throwing a value of the DBError type. So, I've defined them like so:
data DBError = ConversionError ConvertError
| SaveError String
| OtherError String
deriving (Show, Eq)
instance Error DBError where
noMsg = OtherError "No message found"
strMsg s = OtherError s
type DBMonad = ErrorT DBError IO
selectWorkoutByID :: IConnection a => UUID -> a -> DBMonad (Maybe SetRepWorkout)
insertWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
At the level of the calling application, a Workout is a unique object persisted to the database, so the application only ever calls saveWorkout, which itself uses selectWorkoutByID, insertWorkout, and updateWorkout in the ways you would expect:
saveWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
saveWorkout workout conn =
r <- liftIO $ withTransaction conn $ \conn -> runErrorT $ do
w_res <- selectWorkoutByID (uuid workout) conn
case w_res of
Just w -> updateWorkout workout conn >> return ()
Nothing -> insertWorkout workout conn >> return ()
case r of
Right _ -> return ()
Left err -> throwError err
This is ugly. I have to run and unwrap a DBMonad, run that in the IO monad, lift the IO back up into the DBMonad, and then check the results and re-wrap the results in the DBMonad.
How can I do this with less, and easier to read, code?
I'm expecting that using my custom application monad to handle recoverable errors would help me to reduce the amount of code I have to write, but this is doing the opposite!
Here are some additional questions:
Is there a better way to build up application-semantic errors?
Should I be using Control.Exception instead?
After reviewing http://en.wikibooks.org/wiki/Haskell/Monad_transformers, which is the first document on Monad Transformers that really helped me understand them, I figured out a decent solution.
A new version of the saveWorkout function would look like this:
saveWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
saveWorkout workout conn =
ErrorT $ liftIO $ withTransaction conn $ \conn -> runErrorT $ do
w_res <- selectWorkoutByID (uuid workout) conn
case w_res of
Just w -> updateWorkout workout conn >> return ()
Nothing -> insertWorkout workout conn >> return ()
The deal is this:
withTransaction is returning IO Either DBError (). liftIO has the type MonadIO m => IO a -> m a. ErrorT is the standard constructor for everything of the ErrorT monad, and I defined DBMonad to be of that monad. So, I am working with these types:
withTransaction conn $ <bunch of code> :: IO (Either DBError ())
liftIO :: MonadIO m => IO (Either DBError ()) -> m (Either DBError ())
ErrorT :: IO (Either DBError ()) -> ErrorT IO DBError ()
Ideally, since ErrorT/DBMonad are part of the MonadTrans class, I would use simply lift in order to lift IO (Either DBError ()) back up into the ErrorT monad, but at this time I cannot get it to actually type check correctly. This solution, however, still makes the code better by removing the redundent re-wrapping that I had before.