如何调试Haskell代码?

54

我有一个问题。我写了一个很大的Haskell程序,它总是能处理小输入。但现在,当我想测试它并生成更大的输入时,我总是收到以下消息:

HsProg: Prelude.head: empty list

我经常使用Prelude.head。有什么方法可以找出更多信息或获得更好的错误输出以获取发生错误的代码行?


希望这能对您有所帮助:如何使用printf调试Haskell? - Sudantha
6
这并不是对你问题的直接回答,但你可能应该考虑重构你的代码。在首位使用head通常听起来都不是一个好主意。 - C. A. McCann
你应该学会使用 'Debug.Trace (trace)'。 - wuxb
9
不要使用“head”,这很糟糕,正如你刚才注意到的那样。 - augustss
2
如何使用安全版本的head可能值得单独在 Stack Overflow 上提问。虽然您可能不想使用任何一个版本,而是坚持使用模式匹配。 - Tyler
显示剩余4条评论
4个回答

84

GHCi选项-fbreak-on-exception很有用。以下是一个调试会话的示例。首先,我们将文件加载到GHCi中。

$ ghci Broken.hs
GHCi, version 7.0.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
[1 of 1] Compiling Main             ( Broken.hs, interpreted )
Ok, modules loaded: Main.

现在,我们打开-fbreak-on-exceptions并跟踪我们的表达式(在本例中为整个程序的main函数)。

*Main> :set -fbreak-on-exception
*Main> :trace main
Stopped at <exception thrown>
_exception :: e = _

我们遇到了一个异常。让我们尝试使用:list查看代码。

[<exception thrown>] *Main> :list
Unable to list source for <exception thrown>
Try :back then :list

由于异常发生在Prelude.head中,我们无法直接查看源代码。但是根据GHCi的提示,我们可以使用:back命令来尝试列出跟踪中发生的先前事件。

因为异常发生在Prelude.head函数中,所以我们无法直接查看其源代码。但根据GHCi的提示,我们可以使用:back命令查看发生异常之前的调用堆栈信息。

[<exception thrown>] *Main> :back
Logged breakpoint at Broken.hs:2:23-42
_result :: [Integer]
[-1: Broken.hs:2:23-42] *Main> :list
1  
2  main = print $ head $ filter odd [2, 4, 6]
3  
在终端中,有一个突出显示的加粗字体表达式filter odd [2, 4, 6],在这种情况下,这个表达式求值为空列表。如需了解有关如何使用GHCi调试器的更多信息,请参阅 GHC用户指南

太棒了。我没有寻找 GHCi 调试器的信息,但这真的很酷。 - David V.
4
-fbreak-on-exception已更改为-fbreak-on-error。 - holdenlee
@PetrGladkikh 404 - user234461
https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html - Petr Gladkikh

10

您可能想要查看Haskell Wiki - Debugging,其中包含许多有用的方法来解决您的问题。

一个有前途的工具是LocH,它可以帮助您定位代码中触发“空列表”错误的head调用。

个人建议使用safe包,它允许注释大多数Prelude中的partial函数(从而导致更加谨慎地使用这些partial函数),或者更好的是,使用总的函数变体,例如head,它们始终返回结果(如果输入值至少被定义)。


4
我建议尽量不使用head,特别是对于刚接触这门语言的人。 - C. A. McCann
1
@camccann 我完全同意...可惜我看过的大多数入门材料(包括现代化的LYAH)都是从解释head开始,并警告不要将其应用于空列表... :-/ - hvr
1
如果按照我的想法,我会将它(以及tail(!!)和其他一些东西)从Prelude中完全删除,并且根本不向初学者提及它。但是,唉。 - C. A. McCann
@hvr,loch在GHC 7中无法构建。loch-th对我来说可以工作。但似乎它没有像loch一样的预处理器支持。 - edwardw
2
你的 LocH 链接给了我一个 403 错误,但我猜 http://hackage.haskell.org/package/loch 是同样的工具? - Tyler
@MatrixFrog,是同一个工具,但该页面包含使用示例...看起来他们最近更改了访问配置,但您仍然可以通过 archive.org 访问。 - hvr

2

自从GHC 8版本以后,你可以使用 GHC.Stack 模块或者一些详细描述在 Simon博客 上的性能分析编译器标志。


2
您可以使用这个库: Debug.Trace 您可以用以下函数替换任何值 a: trace :: String -> a -> aputStrLn 不同的是,输出中没有 IO。例如:
>>> let x = 123; f = show
>>> trace ("calling f with x = " ++ show x) (f x)
calling f with x = 123
123

追踪函数只应用于调试或监控执行。该函数不是引用透明的:其类型表明它是一个纯函数,但它具有输出追踪消息的副作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接