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

Joh*_*ins 1 node.js purescript

我正在致力于在 Purescript 中构建一个加密套利机器人,主要是为了了解函数式编程范例。作为一名 JS 程序员,我发现 Purescript 的错误消息非常难以解释。目前,在尝试使用 Affjax 库发出 http 请求时,我遇到了“TypesDoNotUnify”错误。我的代码如下所示:

\n
import Affjax.ResponseFormat (ResponseFormat(..))\nimport Affjax.ResponseFormat as ResponseFormat\nimport Data.Argonaut.Core (Json, fromString, stringify)\nimport Data.Either (Either(..))\nimport Data.HTTP.Method (Method(..))\nimport Effect (Effect)\nimport Effect.Aff (Aff, launchAff_)\nimport Effect.Console (log)\nimport Node.Express.App (App, listenHttp, get)\nimport Node.Express.Response (send)\nimport Node.HTTP (Server)\nimport Node.HTTP.Client (method)\n\nmakeQuoteRequest :: String -> String -> String -> String\nmakeQuoteRequest fromAddress toAddress amount = "https://api.1inch.exchange/v3.0/137/quote?fromTokenAddress=" <> fromAddress <> "&toTokenAddress=" <> toAddress <> "&amount=" <> amount\n\nsendQuoteRequest :: Either Error Unit\nsendQuoteRequest = launchAff_ do\n  result <- AX.request(AX.defaultRequest {\n       url = makeQuoteRequest "0xd6df932a45c0f255f85145f286ea0b292b21c90b" "0xc2132d05d31c914a87c6611c10748aeb04b58e8f" "1000000000000000000"\n      ,method = Left GET\n      ,responseFormat = ResponseFormat.json\n      })\n  case result of\n    Left err -> log $ "Quote failed: " <> AX.printError err \n    Right response -> log $ "GET /api response: " <> stringify response.body\n\napp :: App\napp = do\n    get "/" $ send "<a href='http://localhost:8080/quotes'><button>Get Quotes</button></a>"\n    get "/quotes" $ send "This is where your quotes should go"\n\nmain :: Effect Server\nmain = do\n    listenHttp app 8080 \\_ ->\n        log $ "Listening on " <> show 8080\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

printError :: 错误 \xe2\x86\x92 字符串\n无法匹配类型

\n

影响

\n

与类型

\n

阿夫

\n

在尝试将类型 Effect Unit\n与类型 Aff t0\n 匹配时检查该表达式(应用日志)((append "Quote failed: ") (printError err))\n具有类型 Aff t0\nin 值声明 sendQuoteRequest

\n

其中 t0 是未知类型\nPureScript(TypesDoNotUnify)

\n
\n

我希望了解的不仅是如何修复此错误,还希望了解更多有关如何解释 Purescript 给我的错误的信息。如果您的代码中出现此错误,您将采取哪些步骤来解决该错误?

\n

Fyo*_*kin 7

错误消息中的第一句话是:

无法将类型Effect与类型匹配Aff

然后它澄清了一点:

当尝试将类型Effect Unit与类型匹配时Aff t0

这就是绝大多数类型错误的样子。这意味着编译器正在跟踪您的程序,找出哪些位具有哪些类型,最后找到了一个位,该位从一条推理中结果具有类型Effect Unit,但从另一条推理中结果发现具有类型Aff t0(对于某些类型尚不清楚t0)。这两行推理都是正确的,但它们的结果不匹配,因此编译器不知道下一步要做什么。

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

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

在检查表达式是否(apply log) ((append "Quote failed: ") (printError err))具有类型时Aff t0

在这里,我认为编译器可以更好地打印出表达式。它最终会到达那里,但目前它只需要很少的努力就可以打印出来。但没有恐惧!我们仍然可以解码它。

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

好吧,您确实在代码中编写了它:美元运算符是(请参阅文档$)的别名。同样,该运算符是 的别名。apply<>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
Run Code Online (Sandbox Code Playgroud)

你看!这是你在第 25 行写的内容!

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

我们来看看这段代码是什么。看:这是对log函数的调用Effect.Console参见文档)。它的返回类型是Effect Unit. 啊哈!这就是为什么编译器认为类型应该是Effect Unit!因为它是调用函数的结果log

好吧,很好,但是呢Aff t0?好吧,让我们看看该表达式的结果最终会去哪里:作为参数launchAff_请参阅文档)。采用什么类型的参数launchAff_?惊喜——是的Aff a

这样,谜题就解决了:launchAff_需要一个类型为 的参数Aff a,但你给它一个类型为 的值Effect Unit。难怪编译器会抱怨!


至于如何解决。

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

    Left err -> liftEffect $ log $ "Quote failed: " <> AX.printError err 
Run Code Online (Sandbox Code Playgroud)

繁荣!完毕。

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

所以我可能会去寻找合适的类型,例如String -> Aff Unit. 看看:实际上有一个log函数适合。它做同样的事情,但它并不Effect像您正在使用的那样专门在 中工作,而是在任何具有MonadEffect实例的 monad 中工作。Aff这样的单子吗?好吧,让我们搜索一下Aff是的,是的。它确实有一个实例。MonadEffect

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