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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With