Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improve Code: Remove Nested Eithers and Code Duplication

Tags:

purescript

I'm looking for feedback on writing idiomatic PureScript code. This code below is sample code to read from a Twitter API. The signature of the helper methods are:

-- read consumer credentials from a config file
readConfig :: String -> Aff (Either String TwitterCredentials)

-- get the bearer authentication using the consumer credentials
getTokenCredentials :: TwitterCredentials -> Aff (Either String BearerAuthorization)

-- read the Twitter API using the bearer authentication
showResults :: BearerAuthorization -> String -> Aff (Either String SearchResults)

My code is:

main :: Effect Unit
main = launchAff_ do
  let searchTerm = "PureScript"
  config <- readConfig "./config/twitter_credentials.json"
  case config of
    Left errorStr -> errorShow errorStr
    Right credentials -> do
      tokenCredentialsE <- getTokenCredentials credentials
      case tokenCredentialsE of
        Left error ->
          errorShow error
        Right tokenCredentials -> do
          resultsE <- showResults tokenCredentials searchTerm
          case resultsE of
            Left error ->
              errorShow error
            Right result ->
              liftEffect $ logShow $ "Response:" <> (show result.statuses)

As you can see, there is a lot of nested Either statements and I call errorShow three times. How would you write this code making it more readable and possibly removing the code duplication?

like image 549
RAbraham Avatar asked Mar 26 '26 15:03

RAbraham


1 Answers

You can transform your helper functions from returning Aff (Either String a) to ExceptT String Aff a. ExceptT is a monad transformer that carries around Either e a in place of the value, which means that your compiled code would look roughly the same. But at the source level, you get to ignore the errors until the end and thus gain readability and reduce duplication.

If you control the source of the helper functions, just rewrite them directly: instead of returning Left, use throwError, and instead of returning Right, use pure.

If, on the other hand, you don't control the helpers' source code, you can transform them with another small helper function:

eitherToExcept :: forall e a. Aff (Either e a) -> ExceptT e Aff a
eitherToExcept action = either throwError pure <$> lift action

Now your main function can do all the work in the ExceptT monad, letting it propagate the errors behind the scenes, and only at the end use runExceptT to convert the result back to Either:

main = launchAff_ $ either errorShow (liftEffect <<< logShow) $ runExceptT do
    let searchTerm = "PureScript"
    credentials <- eitherToExcept $ readConfig "./config/twitter_credentials.json"
    tokenCredentials <- eitherToExcept $ getTokenCredentials credentials
    results <- eitherToExcept $ showResults tokenCredentials searchTerm
    pure $ "Response:" <> (show results.statuses)

P.S. There may be some typos here and there, as I didn't have time to compile and verify the code.

like image 148
Fyodor Soikin Avatar answered Mar 29 '26 18:03

Fyodor Soikin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!