您如何理解 Purescript 中的错误消息?

How do you understand error messages in Purescript?

我正在使用 Purescript 构建一个加密货币套利机器人,主要是为了了解函数式编程范例。作为主要的 JS 程序员,我发现 Purescript 的错误消息很难解释。目前,在尝试使用 Affjax 库发出 http 请求时,我 运行 陷入 'TypesDoNotUnify' 错误。我的代码如下所示:

import Affjax.ResponseFormat (ResponseFormat(..))
import Affjax.ResponseFormat as ResponseFormat
import Data.Argonaut.Core (Json, fromString, stringify)
import Data.Either (Either(..))
import Data.HTTP.Method (Method(..))
import Effect (Effect)
import Effect.Aff (Aff, launchAff_)
import Effect.Console (log)
import Node.Express.App (App, listenHttp, get)
import Node.Express.Response (send)
import Node.HTTP (Server)
import Node.HTTP.Client (method)

makeQuoteRequest :: String -> String -> String -> String
makeQuoteRequest fromAddress toAddress amount = "https://api.1inch.exchange/v3.0/137/quote?fromTokenAddress=" <> fromAddress <> "&toTokenAddress=" <> toAddress <> "&amount=" <> amount

sendQuoteRequest :: Either Error Unit
sendQuoteRequest = launchAff_ do
  result <- AX.request(AX.defaultRequest {
       url = makeQuoteRequest "0xd6df932a45c0f255f85145f286ea0b292b21c90b" "0xc2132d05d31c914a87c6611c10748aeb04b58e8f" "1000000000000000000"
      ,method = Left GET
      ,responseFormat = ResponseFormat.json
      })
  case result of
    Left err -> log $ "Quote failed: " <> AX.printError err 
    Right response -> log $ "GET /api response: " <> stringify response.body

app :: App
app = do
    get "/" $ send "<a href='http://localhost:8080/quotes'><button>Get Quotes</button></a>"
    get "/quotes" $ send "This is where your quotes should go"

main :: Effect Server
main = do
    listenHttp app 8080 \_ ->
        log $ "Listening on " <> show 8080

VsCode 突出显示以 Left err -> log 开头的行作为问题的根源,当我将鼠标悬停在该错误上时,我得到以下附加信息:

printError :: Error → String Could not match type

Effect

with type

Aff

while trying to match type Effect Unit with type Aff t0 while checking that expression (apply log) ((append "Quote failed: ") (printError err)) has type Aff t0 in value declaration sendQuoteRequest

where t0 is an unknown type PureScript(TypesDoNotUnify)

我希望了解的不仅是如何修复这个错误,而是更多地了解如何解释 Purescript 给我的错误。如果您的代码中出现此错误,您会采取什么步骤来解决?

错误消息中的第一个单词是:

Could not match type Effect with type Aff

然后它澄清了一点:

while trying to match type Effect Unit with type Aff t0

这是绝大多数类型错误的样子。这意味着编译器一直在跟踪你的程序,弄清楚哪些位有哪些类型,最后发现一个位从一条推理结果是 Effect Unit 类型,但从另一行推理结果是 Effect Unit类型 Aff t0(对于某些未知类型 t0)。两行推理都正确,但结果不匹配,所以编译器不知道下一步该做什么。

你问的那些推理是什么?好吧,编译器并没有告诉你,这有一个很好的理由:在大多数实际情况下,它对你来说无论如何都没有任何意义。从编译器的角度来看,推理线中的所有位都同样重要,但是它们太多了,无法一次打印出来,而且它不知道如何选择对你最有意义的那些。

但它确实为您提供了另一条有价值的信息 - 结果表明该位具有两种不匹配的类型:

while checking that expression (apply log) ((append "Quote failed: ") (printError err)) has type Aff t0

在这里我认为编译器可以更好地打印出表达式。它最终会到达那里,但现在它只需要最小的努力就可以打印出来。但不要害怕!我们仍然可以解码它。

看到那边的 apply 了吗?那是什么?你没有把它写在你的代码中,那么它是从哪里来的?

好吧,您确实在代码中写了它:美元运算符 $apply (see docs) 的别名。同样,运算符 <>append.

的别名

知道了,我们可以将这段代码解码成它的原始形式:

(apply log) ((append "Quote failed: ") (printError err))
(($) log) (((<>) "Quote failed: ") (printError err))
($) log ((<>) "Quote failed: " (printError err))
log $ ("Quote failed: " <> (printError err))
log $ "Quote failed: " <> printError err

嘿,看!这是您在第 25 行写的位!

好的,到目前为止我们知道什么?我们知道编译器已经确定 log $ ... 表达式一方面必须具有类型 Effect Unit 并且 必须具有类型 Aff t0 另一方面。

让我们看看这段代码是什么。看:它是从 Effect.Console (see docs) 调用 log 函数。它的 return 类型是 Effect Unit。啊哈!所以 这就是 编译器认为类型应该是 Effect Unit 的原因!因为它是调用 log 函数的结果!

好的,很好,但是 Aff t0 呢?好吧,让我们看看该表达式的结果最终去向:作为 launchAff_ (see docs) 的参数。 launchAff_ 采用什么类型的参数?惊喜 - Aff a!

至此,谜团解开了:launchAff_ 需要一个类型为 Aff a 的参数,但您却给它一个类型为 Effect Unit 的值。难怪编译器会抱怨!


至于如何修复。

第一个愚蠢的方法就是去 Pursuit 上搜索。我们有一个 Effect Unit 类型的值,我们需要将其转换为 Aff a。那可能吗?好吧,让我们search for a function of type Effect Unit -> Aff a。看哪:有这样一个函数,它叫做liftEffect。所以我们可以使用它:

    Left err -> liftEffect $ log $ "Quote failed: " <> AX.printError err 

轰!完成。

但是,如果是我,我通常会这样想:我不可能是第一个遇到这种情况的人。想要从 Aff 上下文中打印到控制台不是很正常吗?不应该已经有一个应用程序吗?

所以我可能会去search for a suitable type, for example String -> Aff Unit. And look: there is actually a log function that fits. It does the same thing, but it works not specifically in Effect, like the one you're using, but in any monad that has a MonadEffect instance. Is Aff such a monad? Well, let's search for Aff and look:是的,是的。它确实有一个 MonadEffect.

的实例

所以现在,知道了所有这些,我可能只是将我的导入从 Effect.Console (log) 更改为 Effect.Class.Console (log),然后我不需要进行任何其他更改:新的 log 函数将只在 Aff.

中工作