An attempt at a Haskell Servant API - repetition of Left/Right case matching I'm attempting to learn more about Haskell by completing a "hobby" project - a simple API to save/retrieve content for pages - used with Hakyll to generate static sites.

I have got something working for the tasks of retrieving all of the content for a site, and for adding a new page to a site. Hooray!

However, the code suffers from extraordinary verbosity.  I suspect I could improve things by bringing in some kind of monad transformer to deal with the IO (Either String ()), but I am not familiar enough with monad transformers to even be able to work out where to start.

siteServer :: Server SiteAPI
siteServer = site :&lt;|&gt; addPage
    where
          site :: Int -&gt; Handler Site
          site i = do
              s &lt;- liftIO $ loadSite i
              case s of 
                  Left err -&gt; throwError err404 { errBody = "Site not found!" `BS.append` (BS.pack err) }
                  Right s' -&gt; return s'

          addPage :: Int -&gt; Page -&gt; Handler ()
          addPage i p = do
              s &lt;- liftIO $ savePage i p
              case s of
                Left err -&gt; throwError err404 { errBody = "Site not found!" `BS.append` (BS.pack err) }
                Right _ -&gt; return ()

          savePage :: Int -&gt; Page -&gt; IO (Either String ())
          savePage i p = do
              s &lt;- loadSite i
              case s of
                Left err -&gt; return $ Left err
                Right s' -&gt; savePage' s' p

              where savePage' :: Site -&gt; Page -&gt; IO (Either String ())
                    savePage' (Site name pages) p = saveSite (Site name (p:pages))

                    saveSite :: Site -&gt; IO (Either String ())
                    saveSite s = catch writeSiteToFile returnError
                              where --returnError :: IOException -&gt; IO (Either String ())
                                    returnError e = return $ Left ("Failed to write to file: " ++ show i ++ ". " ++ show (e :: IOException))
                                    --writeSiteToFile :: IOException -&gt; IO (Either String ())
                                    writeSiteToFile = Right &lt;$&gt; BS.writeFile (getSiteFilename i) (encode s)
Here's how to use ExceptT to brighten the day a little. You may have hoped for a way to do handle only once after combining the Handlers with :&lt;|&gt;... but I also don't know how to do that. If withExceptT went to arbitrary MonadError instances, handle could have been more concise.

import Control.Monad.Trans.Except

siteServer :: Server SiteAPI
siteServer = site :&lt;|&gt; addPage where
  handle :: ExceptT String IO a -&gt; Handler a
  handle = liftIO . runExceptT &gt;=&gt; \case
    Left err -&gt; throwError $ err404 { errBody = "Site not found!" &lt;&gt; BS.pack err }
    Right x -&gt; return x

  site :: Int -&gt; Handler Site
  site i = handle $ ExceptT $ loadSite i

  addPage :: Int -&gt; Page -&gt; Handler ()
  addPage i p = handle $ do
    Site name pages &lt;- ExceptT $ loadSite i
    withExceptT (\e -&gt; "Failed to write to file: " ++ show i ++ ". " ++ show (e :: IOException))
      $ ExceptT $ try $ BS.writeFile (getSiteFilename i) $ encode $ Site name $ p:pages