使用GHCi调试Haskell程序中的无限循环

gsp*_*spr 20 debugging haskell infinite-loop ghc ghci

我第一次在Haskell程序中遇到了无限循环.我已经将它缩小到一个非常具体的代码部分,但我似乎无法精确指出我有一个非终止递归定义的位置.我模糊地熟悉:跟踪和:GHCi中的历史,但问题是我的代码的某些分支涉及相当多的递归修改Data.Map.Map,因为地图x是通过基于值adjust的地图中x'的某些东西获得的在另一张地图上取决于x'.具体细节在这里并不重要,但正如你可能知道的那样,如果这是以交织在一起的递归方式发生的,那么我的通话历史会在地图lookups,adjustments和insert离子所涉及的所有各种比较中完全陷入困境.

任何人都可以推荐一种更有效的方法来定位无限循环?例如,它可以帮助将呼叫历史限制为来自单个源文件的调用.

cro*_*eea 13

我很惊讶没有人提到所有Haskell性能问题普遍存在的响应(无限运行时是"性能问题"的一个相当极端的例子):分析!

我只能使用分析快速识别无限循环.为了完整性,编译-prof -fprof-auto,然后运行程序足够长的时间,在分析统计中应该显示有问题的功能.例如,我希望我的程序在<1秒内完成,所以我让分析器运行大约30秒,然后用Ctrl + C杀死我的程序.(注意:分析会保存增量结果,因此即使在程序运行完成之前终止程序,您仍然可以获得有意义的数据.编辑:除非它没有.)

在.prof文件中,我找到了以下块:

                                                 individual      inherited
COST CENTRE         MODULE     no.    entries   %time  %alloc   %time %alloc
...
primroot.\          Zq         764          3    10.3    13.8    99.5  100.0
 primroot.isGen     Zq         1080   50116042    5.3     6.9    89.2   86.2
  primroot.isGen.\  Zq         1087   50116042   43.4    51.7    83.8   79.3
   fromInteger      ZqBasic    1088          0   40.4    27.6    40.4   27.6
Run Code Online (Sandbox Code Playgroud)

所以有5000万个条目primroot.isGen,而下一个最被调用的函数只有1024个调用.此外,99.5%的运行时用于计算primroot,这似乎非常可疑.我检查了那个函数并很快找到了错误,在我的情况下是一个简单的拼写错误:(`div` foo)而不是(div foo).

我认为值得注意的是,GHC警告不会发现这个问题-fbreak-on-exceptions.代码库很大; 试图通过插入调试语句(任何类型)来追踪问题并没有让我任何地方.我也没有成功使用GHCi调试器,因为历史基本上不存在,HPC没有透露任何有用的东西.


ste*_*ley 9

正如ShiDoiSi所说,"通过眼睛"解决通常是最成功的方式.

如果您在相同的函数中绑定到不同的类似名称的变量x,x'等,您可以尝试在文件顶部启用警告:

{-# OPTIONS -Wall #-} 
Run Code Online (Sandbox Code Playgroud)

如果这是一个问题,你绑定到错误的东西并进行失控的递归,这可能会帮助你发现它 - 例如通过指示意外使用阴影.


Tho*_*son 7

确保你已经完全使用了GHCi调试器,包括设置-fbreak-on-exception(如果你得到的话很有用<<loop>>,是吗?)并确保你已经尝试了Stephen关于使用GHC警告的建议.

如果这些失败(GHCi调试器真的不应该'失败',这只是解释数据的问题)然后尝试在循环的情况下运行HPC,这样你就可以直观地看到未被评估的分支和值,如果它是循环的然后应该完成的事情可能甚至没有被评估,并且将显示在标记的HTML中.

  • [这里](http://book.realworldhaskell.org/read/testing-and-quality-assurance.html)如何实际*使用*HPC. (2认同)

Tad*_*Tad 7

我正在进行长时间的调试,以找到无限循环的原因.我变得非常接近,这对我帮助最大.假设您的循环是由以下内容引起的:

...
x1 = f1 x2 y
x2 = f2 z x3
x3 = f3 y x1
...
Run Code Online (Sandbox Code Playgroud)

因此x1取决于x2,x2取决于x3,x3取决于x1.坏!

在f1,f2,f3的定义中撒上跟踪函数.就像是:

f1 x y | trace ("f1: ") False = undefined
f1 x y = ... -- definition of f1

f2 x y | trace ("f2: ") False = undefined
f2 x y = ... -- definition of f2

-- same for f3
Run Code Online (Sandbox Code Playgroud)

运行程序以查看调用了哪些函数.输出可能是这样的

f3:
f2:
f1: 
<<loop>>
Run Code Online (Sandbox Code Playgroud)

然后开始在跟踪函数中显示一些变量.例如,如果将f2的轨迹更改为

f2 x y | trace ("f2: x: " ++ show x) False = undefined
Run Code Online (Sandbox Code Playgroud)

然后输出将看起来像:

f3:
f2: x: x_value
f1: 
<<loop>>
Run Code Online (Sandbox Code Playgroud)

但是,如果你然后将f2的轨迹更改为

f2 x y | trace ("f2: x: " show x ++ " y: " ++ show y) False = undefined
Run Code Online (Sandbox Code Playgroud)

然后输出将是

f3:
<<loop>>
Run Code Online (Sandbox Code Playgroud)

因为循环依赖不能评估f2的第二个参数.

所以你现在知道无限循环中的一个函数是f2,它的第二个参数(但不是它的第一个)具有循环依赖性.

快乐的调试!