原文: http://en.wikibooks.org/wiki/Haskell/Debugging
借助 Debug.Trace
打印调试信息
打印调试信息是调试程序通用的办法.
命令式语言中, 我们只要设置打印语句到标准输出的代码,
或者到某些 log 文件里, 这样就能追踪调试信息了.
(比如说, 特定变量的值, 或者一些肉眼可读的消息.)
然而对于 Haskell, 除了 IO Monad 以外我们没办法打印任何信息.,
而且我们不像单单为了调试引入那样的例外.
为了解决这个问题, 标准库提供了 Debug.Trace 模块.
模块导出了一个 trace
函数, 提供了一个简便的方法在程序任何位置添加调试信息.
比如说, 下面的程序会打印出每个传给 fib
的不等于 0
或 1
的参数:
haskell
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
同时实现了追踪程序执行步骤, 就是, 比如说, 哪个函数先执行, 哪个后执行.
办法是, 我们在关注的函数里某些部分添加代码, 像这样:
haskall
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 Monad 以外使用, 实际上类型声明是...
haskell
trace :: String -> a -> a
...意味着这是个纯函数. 但确实, trace
打印可用信息时在操作 IO. 怎么回事?
实际上, trace
使用了一个鬼把戏绕过了 IO 和纯的 Haskell 之间的隔离.
这在下面的免责声明里有体现, 见trace
的文档:
trace
函数只应该用于调试, 或者监视执行过程.
这个函数并不是引用透明的: 其类型表明是一个纯函数, 但它存在输出 trace 信息的副作用.
一个 trace
常见的错误用法:
为了让调试追踪信息适应已有的函数, 意外把 trace
消息中计算出来打印包含进来.
比如, 不要写出这样的代码:
haskell
let foo = trace ("foo = " ++ show foo) $ bar in baz
这会造成无限递归, 因为追踪信息会先于 bar
表达式求值,
导致按照追踪信息里 foo
的执行, 然后又是 bar
,
然后追踪信息要在 bar
前求值, 这样直到无穷.
正确的追踪信息应该用 show bar
而不是 show foo
:
haskell
let foo = trace ("foo = " ++ show bar) $ bar in baz
实用的风格
一个揉合进 show
的辅助函数很方便:
haskell
traceThis :: (Show a) => a -> a traceThis x = trace (show x) x
同样地, Debug.Trace
定义了一个 traceShow
函数,
这个函数打印第一个参数, 并且求值到第二个参数(evaluates to the second one):
haskell
traceShow :: (Show a) => a -> b -> b traceShow = trace . show
最后, 一个这样的 debug
函数也证明很便捷:
haskell
debug = flip trace
它能让你这样写代码...
haskell
main = (1 + 2) `debug` "adding"
...让注释/反注释调试语句变得更加容易.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。