改进代码:删除嵌套的 Eithers 和代码重复

Improve Code: Remove Nested Eithers and Code Duplication

我正在寻找有关编写惯用 PureScript 代码的反馈。下面的代码是从 Twitter API 读取的示例代码。辅助方法的签名是:

-- 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)

我的代码是:

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)

如你所见,有很多嵌套的Either语句,我调用了3次errorShow。您将如何编写此代码以使其更具可读性并可能消除代码重复?

您可以将辅助函数从返回 Aff (Either String a) 转换为 ExceptT String Aff aExceptT 是一个 monad 转换器,它用 Either e a 代替值,这意味着您编译的代码看起来大致相同。但在源代码级别,您可以忽略错误直到最后,从而获得可读性并减少重复。

如果你控制了辅助函数的来源,直接重写它们:不返回Left,使用throwError,不返回Right,使用[=19] =].

另一方面,如果您无法控制助手的源代码,您可以使用另一个小的助手函数来转换它们:

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

现在你的 main 函数可以完成 ExceptT monad 中的所有工作,让它在幕后传播错误,并且只在最后使用 runExceptT 转换结果返回 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。由于我没有时间编译和验证代码,可能会有一些错别字。