如何使用 PureScript 进行调试?

问题描述 投票:0回答:3

问题

以下是一个最小的、人为的示例:

read :: FilePath -> Aff String
read f = do
  log ("File: " <> f) -- (1)
  readTextFile UTF8 f -- (2)

我想在

(1)
上发生潜在错误之前在
(2)
中进行一些调试日志记录。到目前为止,在 Spago REPL 中执行以下代码适用于成功案例:

$ spago repl
> launchAff_ $ read "test/data/tree/root.txt"
File: test/data/tree/root.txt
unit

问题:如果

(2)
- 文件是目录 - 这里出现错误,
(1)
似乎根本没有执行:

$ spago repl
> launchAff_ $ read "test/data/tree"
~/purescript-book/exercises/chapter9/.psci_modules/node_modules/Effect.Aff/foreign.js:532
                throw util.fromLeft(step);
                ^

[Error: EISDIR: illegal operation on a directory, read] {
  errno: -21,
  code: 'EISDIR',
  syscall: 'read'
}

原来的问题更复杂,包括多层递归(参见电子书练习3),我需要记录日志来调试上述错误。

问题

  1. 无论这里即将出现错误,我如何正确记录?
  2. (可选)是否有更复杂、完善的调试替代方案 - purescript-debugger?专用的 VS Code 调试扩展/功能将是锦上添花。
debugging logging visual-studio-code functional-programming purescript
3个回答
7
投票

首先,您观察到的症状并不意味着第一行没有执行。它总是执行,只是由于控制台在 PureScript REPL 中的工作方式,您看不到它的输出。输出被吞噬。遗憾的是,这并不是 REPL 的唯一问题。

您可以通过将

log
替换为
throwError
并观察总是抛出错误来验证第一行是否始终执行。或者,您可以使第一行修改可变单元格,而不是写入控制台,然后检查单元格的内容。

最后,这只发生在 REPL 中。如果您将

launchAff_
调用放入
main
并运行该程序,您将始终获得控制台输出。


现在讨论手头的实际问题:如何调试跟踪。

如果你能负担得起的话,登录到控制台就可以了,但是还有一种更优雅的方式:

Debug.trace

这个函数有一个隐藏的效果——即它的类型说它是纯粹的,但在调用时它确实产生了效果。这个小谎言让你可以在纯设置中使用

trace
,从而调试纯代码。不需要
Effect
!只要仅用于调试就可以,但不要将其放入生产代码中。

它的工作方式是它需要两个参数:第一个参数被打印到控制台,第二个参数是打印后要调用的函数,整个结果就是该函数返回的结果。例如:

calculateSomething :: Int -> Int -> Int
calculateSomething x y =
    trace ("x = " <> show x) \_ ->
        x + y

main :: Effect Unit
main =
  log $ show $ calculateSomething 37 5

>  npx spago run       
'x = 37'               
42                     

第一个参数可以是任何东西,而不仅仅是字符串。这可以让您轻松打印很多东西:

calculateSomething :: Int -> Int -> Int
calculateSomething x y =
    trace { x, y } \_ ->
        x + y

>  npx spago run
{ x: 37, y: 5 }
42

或者,将其应用到您的代码中:

read :: FilePath -> Aff String
read f = trace ("File: " <> f) \_ -> do
  readTextFile UTF8 f

但这里有一个微妙的细节:一旦您 call

read
,这种跟踪就会发生,即使最终的
Aff
永远不会被实际执行。如果您需要在有效执行时进行跟踪,则需要将
trace
调用作为操作的一部分,并注意不要将其作为序列中的第一个操作:

read :: FilePath -> Aff String
read f = do
  pure unit
  trace ("File: " <> f) \_ -> pure unit
  readTextFile UTF8 f

当然,每次需要在有效的上下文中进行跟踪时都执行此操作有点不方便,因此有一个特殊的函数可以为您完成此操作 - 它称为

traceM
:

read :: FilePath -> Aff String
read f = do
  traceM ("File: " <> f)
  readTextFile UTF8 f

如果您查看其源代码,您会发现它的作用与我在上面的示例中所做的完全一样。


可悲的是,当异常发生时,

trace
不会在REPL中帮助你,因为它仍然打印到控制台,所以它仍然会因为同样的原因被吞没。

但即使它没有被吞没,输出也会有点乱码,因为

trace
实际上以颜色输出(以帮助您在其他输出中区分出来),而 PureScript REPL 与颜色有复杂的关系:

> calculateSomething 37 5
←[32m'x = 37'←[39m
42

7
投票

除了 Fyodor Soikin 的精彩答案之外,我还发现了一个使用 VS Code 调试视图的变体。

1.) 确保使用源映射构建:

spago build --purs-args "-g sourcemaps"

2.) 将调试配置添加到 VS Code

launch.json
:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": ["<node_internals>/**"],
      "runtimeArgs": ["-e", "require('./output/Main/index.js').main()"],
      "smartStep": true // skips files without (valid) source map
    }
  ]
}

"./output/Main/index.js"
/
.main()
替换为编译后的
.js
要调试的文件/函数。

3.) 设置断点并通过源映射支持单步执行

.purs
文件。


0
投票

添加A_blop答案的更新版本,因为已经有一段时间了,答案不再有效,但核心解决方案是相同的。

  1. 像往常一样使用源映射构建,但现在它带有 spago

    spago bundle --source-maps

  2. 将调试配置添加到 VS Code launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/index.js",
            "outFiles": ["${workspaceFolder}/index.js.map"],
            "smartStep": true 
        }
    ]
}

you can now set a breakpoint and attach a debugger although its kinda janky and wont step on the 10th line for me.

© www.soinside.com 2019 - 2024. All rights reserved.