Haskell/调试
调试打印是调试程序的常见方法。在命令式语言中,我们可以随意在代码中添加打印语句到标准输出或一些日志文件中,以跟踪调试信息(例如,特定变量的值,或一些人类可读的訊息)。然而,在 Haskell 中,我们无法输出任何信息,除了通过 IO 单子;并且我们不希望仅仅为了调试而引入它。
为了解决这个问题,标准库提供了 Debug.Trace。该模块导出一个名为 trace
的函数,它提供了一种在程序的任何地方方便地添加调试打印语句的方法。例如,这个程序打印传递给 fib
的每个参数,当它不等于 0 或 1 时。
module Main where
import Debug.Trace
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = trace ("n: " ++ show n) $ fib (n - 1) + fib (n - 2)
main = putStrLn $ "fib 4: " ++ show (fib 4)
以下是结果输出
n: 4 n: 3 n: 2 n: 2 fib 4: 3
此外,trace
使跟踪程序的执行步骤成为可能;也就是说,哪个函数首先调用,哪个函数其次调用等等。为此,我们可以注释我们感兴趣的函数的部分,像这样
module Main where
import Debug.Trace
factorial :: Int -> Int
factorial n | n == 0 = trace ("branch 1") 1
| otherwise = trace ("branch 2") $ n * (factorial $ n - 1)
main = do
putStrLn $ "factorial 6: " ++ show (factorial 6)
当以这种方式注释的程序运行时,它将按注释语句执行的顺序打印调试字符串。该输出可能有助于在缺少语句或类似情况的情况下定位错误。
如上所示,trace
可以在 IO 单子之外使用;事实上,它的类型签名...
trace :: String -> a -> a
...表明它是一个纯函数。然而,trace
在打印有用訊息时,确实在执行 IO 操作。到底发生了什么?实际上,trace
使用了一种技巧来绕过 IO 和纯 Haskell 之间的隔离。这体现在 trace
文档 中的以下免责声明中。
trace
函数应仅用于调试或监控执行。该函数不是引用透明的:它的类型表明它是一个纯函数,但它具有输出跟踪訊息的副作用。
使用 trace
的一个常见错误:在试图将调试跟踪融入现有函数时,有人意外地将正在计算的值包含在要由 trace
打印的訊息中;例如,不要做类似以下的事情
let foo = trace ("foo = " ++ show foo) $ bar
in baz
这会导致无限递归,因为跟踪訊息将在 bar
表达式之前计算,这将导致 foo
的计算取决于跟踪訊息和 bar
,而跟踪訊息将在 bar
之前计算,依此类推,无限循环。应该使用 show bar
而不是 show foo
作为正确的跟踪訊息。
let foo = trace ("foo = " ++ show bar) $ bar
in baz
一个包含 show
的辅助函数可能很方便
traceThis :: (Show a) => a -> a
traceThis x = trace (show x) x
类似地,Debug.Trace
定义了一个 traceShow
函数,它“打印”第一个参数,并计算为第二个参数
traceShow :: (Show a) => a -> b -> b
traceShow = trace . show
最后,像这样的 debug
函数可能也会很方便
debug = flip trace
这将允许您编写类似以下的代码...
main = (1 + 2) `debug` "adding"
... 使注释/取消注释调试语句变得更容易。
此页面是一个 存根。您可以通过 扩展它 来帮助 Haskell。 |