HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

An attempt at a Haskell Servant API - repetition of Left/Right case matching

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
caseleftattemptservanthaskellrepetitionapimatchingright

Problem

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 : addPage
    where
          site :: Int -> Handler Site
          site i = do
              s  throwError err404 { errBody = "Site not found!" `BS.append` (BS.pack err) }
                  Right s' -> return s'

          addPage :: Int -> Page -> Handler ()
          addPage i p = do
              s  throwError err404 { errBody = "Site not found!" `BS.append` (BS.pack err) }
                Right _ -> return ()

          savePage :: Int -> Page -> IO (Either String ())
          savePage i p = do
              s  return $ Left err
                Right s' -> savePage' s' p

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

                    saveSite :: Site -> IO (Either String ())
                    saveSite s = catch writeSiteToFile returnError
                              where --returnError :: IOException -> IO (Either String ())
                                    returnError e = return $ Left ("Failed to write to file: " ++ show i ++ ". " ++ show (e :: IOException))
                                    --writeSiteToFile :: IOException -> IO (Either String ())
                                    writeSiteToFile = Right  BS.writeFile (getSiteFilename i) (encode s)

Solution

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 :... 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 : addPage where
  handle :: ExceptT String IO a -> Handler a
  handle = liftIO . runExceptT >=> \case
    Left err -> throwError $ err404 { errBody = "Site not found!" <> BS.pack err }
    Right x -> return x

  site :: Int -> Handler Site
  site i = handle $ ExceptT $ loadSite i

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

Code Snippets

import Control.Monad.Trans.Except

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

  site :: Int -> Handler Site
  site i = handle $ ExceptT $ loadSite i

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

Context

StackExchange Code Review Q#149009, answer score: 2

Revisions (0)

No revisions yet.